Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
NESUG 17 Applications Running SAS® Scripts Embedded within JSP Tags through SAS/Connect Java API Syloke J. Soong, Allegro MicroSystems, Inc. large amounts of adhoc records are to be constructed and inserted, it is also a development and debugging convenience to construct plain records over using SQL insert-values statements. (On the other hand, it would be preferable if SAS Institute would decide to allow use of macros as CARDS records. Why not, is a question SAS Institute should oblige to answer.) Abstract A SAS script executes from a command line by invoking <sasroot>/sas <scriptname>.sas. There are three ways of embedding and running that script from a JSP. They are 1. Using WebAF (AppDev Studio) APIs and tag libraries. 2. Using SAS/Intrnet. 3. Using SAS/Connect Java API. Imagine there exists a legacy suite of SAS scripts with strong motivation to migrate to J2EE as well as to a new data schema. These are the plans. 1. 2. The paper briefly discusses the differences between the three and focuses on the last method. 1. Description of a custom JSP tag to accept blocks of text. 2. Embedding a SAS script within the JSP tag for submission to the server. 3. Eliciting a SAS/Connect connection. 4. How the JSP retrieves the output of the SAS script. 5. Eliciting a single user JDBC connection from the SAS/Connect connection. 6. Submitting SQL blocks embedded in the JSP to the JDBC connection. 7. Comparison between SAS/Share JDBC and SAS/Connect JDBC. 8. Migrating a legacy SAS report to the web easily. 9. Selective incremental conversion of legacy SAS reports to SAS Java technologies. 3. 4. 5. Deploy SAS/Connect and SAS/Share. Move SAS jobs lock-stock-and-barrel into J2EE, without any change to the scripts. Gradually and selectively modify SAS scripts to Java. Design a new schema compatible to real-time loading and analysis as well as corrective reloading. Modify loading procedure to load into both new tables as well as deprecated columns. The following are quality improvement objectives of the plans. 1. 2. 3. Introduction The inspiration that initiated the solution described in this discussion was attempts to submit SAS scripts from a servlet/JSP or Java class to a SAS/Connect session and then retrieving any SAS/Graph objects generated directly from that connection, without involving the use of external services like FTP, NFS, SMB or SAS/Intrnet. With the new schema, the thousands of weekly regenerated menu hierarchy HTML files would be generated dynamically by a single JSP. With SAS/Connect, SAS/Share, JSP and JDBC, all HTML and excel reports will also be generated dynamically. Finally, generate charts dynamically – without deploying SAS/Intrnet or WebAF. Without using FTP, PIPE or SOCKET FILENAMEs. The discussion requires a comprehension of JSP authoring and terminology. All the example codes are simplified and exception handlers removed. Some class libraries, e.g. JUtil, will not be discussed. The JSP Text Tag To illustrate exploiting this solution, a scenario involving the migration of legacy SAS jobs generating static reports to a J2EE dynamic generation of SAS graphs, reports and menus drawn from real-time dataset queries. The term “text object” will mean an instance of the custom text tag. When declared with an ID, the text object is instantiated, Additional inspiration towards this solution was frustration over the limitation of SAS macros, especially the limitation where SAS macros cannot be used as CARDS records. Dynamic CARDS records is an essential convenience in implementing a dynamic web framework. It avoids the hassle of managing file system resources to construct input data files at the remote SAS session. When <nesug:text id="menu_sql" scope="request"> Therefore, the compiler would not allow more than one text tag ID to be declared within the same java code block, as in the case of any java variable. 1 NESUG 17 Applications <nesug:text ref="LoadWeekly_sas" id="LoadWeekly_sas" scope="request"/> When declared with a REF, the text object is not instantiated but referenced. 5. LoadWeekly_sas SAS script is submitted: <nesug:text ref="menu_sql" scope="request"> Connect.submitLines (""+LoadWeekly_sas); The compiler will throw an error if a REF is made without a preceding ID declaration. When invoked as REF, the text tag is also on “append” mode. A text tag invoked with REF within an iteration loop would allow it to accumulate text. A text tag invoked with ID within an iteration block would cause it to be reinstantiated per iteration. JSP custom tag technology also allows Java code within a tag enclosure, another way text could also be accumulated. LoadWeekly.jsp: <%@taglib uri="/WEB-INF/nesug-tags.tld" prefix="nesug"%> <%@page import="example.jdbc.JUtil" %> <%@page import="javax.mail.*" %> <%@page import="javax.mail.internet.*" %> <%@include file="LoadWeekly.jspf"%> <%@include file="MkSASConnect.jspf"%> The body text is accumulated into a StringBuffer. At anytime, the accumulated text to be reproduced using its ID/REF handle: <nesug:text ref="LoadWeekly_sas" id="LoadWeekly_sas" scope="request"/> <% Connect.submitLines(""+LoadWeekly_sas); <%=menu_sql%> LoadWeekly.jspf: When a tag is declared in one JSP it can be referenced by another JSP within the declared scope persistence. This allows accumulating text in one JSP but used by another. The referencing page must declare it with both the ID and REF attributes, <nesug:text id="LoadWeekly_sas" scope="request"> %macro loadprod(location); filename wkdata "//DataMgr/Manuf/$location..dat"; %if %sysfunc(fexist(wkdata)) %then %do; data lastwk; attrib end_date informat yymmdd10.; attrib lot format $15; attrib location format $2; attrib division format $10; attrib product format $4; attrib param format $20; attrib value informat 8.3 file wkdata; location = "$location"; input end_date yymmdd10. lot $ division $ product $ param $ value; run; %end; %mend; <nesug:text id="menu_sql" ref=”menu_sql” scope="request"> Using the Custom JSP Tag Technology The following is the script of a simple SAS job loading weekly data. It is copied and pasted from a scheduled production job. It is enclosed within a custom JSP text tag <nesug:text>, in order for a JSP to send it as a text stream to the SAS server and execution is invoked through SAS/Connect. 1. LoadWeekly.jsp calls LoadWeekly.jspf. %loadprod(VA); %loadprod(MA); %loadprod(CA); 2. The object Connect is the SAS/Connect connection %let srv=prodshr.example.mydomain; libname prod server=srv.__5010; instantiated when LoadWeekly.jspf calls SASConnect.jspf proc append base=prod.weekly; data=lastwk force; run; </nesug:text> <%@include file="SASConnect.jspf"%> 3. LoadWeekly.jsp calls LoadWeekly.jspf to declare the text object LoadWeekly_sas. The text object is declared persistent within a request. Refer to the appendix for java code for set-up of and initiating a session SAS/Connect. Job completion notification email is constructed and sent. 4. The LoadWeekly_sas text object is resurrected by LoadWeekly.jsp: 1. The text object transforms the query results stored in String[] verify into HTML table: 2 NESUG 17 Applications Scheduling a J2EE Job <nesug:text id="JobStatus_html" scope="request"> 2. The text object is included as a message body: The job is run as a J2EE URL and therefore requires the scheduler to trigger the URL of the JSP containing the SAS script. A Java application, RunUrl, is compiled using JBUilder, which can generate either a Windows or Solaris executable file. 3. The message is sent: RunUrl can be scheduled from any system with a scheduler and JRE 1.4, mailattrs.put(JobStatus_html, mmsgs); sendmail.send(mailattrs, true); RunUrl http://wwwprod/batchjobs/manuf/capability .jsp?code=7of9 <nesug:text id="Verify_sql" scope="request"> select location, product, division, count(distinct lot) from lastwk </nesug:text> <% Object[][] verify = JUtil.SqlFetch( Connect.jdbcConnection, ""+Verify_sql); Connect.disconnect(); %> Having the URL parameter “code” prevents inadvertent triggering of the job by URL crawlers. Where at the beginning of the JSP, the job would not run unless the parameter “code” is satisfied: code = request.getParameter("code"); if (code==null || code.notequals("7of9") return; <nesug:text id="JobStatus_html" scope="request"> <html> Summary of weekly data loaded.<br> <table border=1 align=left> <tr> <th>Location</th> <th>Product</th> <th>Division</th> <th>Number of Lots</th> </tr> <% for (int i=0; i<verify.length; i++) {%> <tr> <% for (int j=0; j<verify[i].length; j++) {%> <td><%=verify[i][j]%> <% }%> </tr> <% }%> </table> </html> </nesug:text> The code for RunUrl.java is in the appendix. Treating the Legacy Report The following legacy SAS script (simplified) has been running weekly to generate a set of static reports. It iteratively calls 1. 2. 3. mkcaprpts macro (not shown) to generate html and excel reports. mkshewhart macro to generate boxcharts. mkchoices to cumulatively refresh a three level hierarchical choice board. %macro capabilityreport(); %let srv=prodshr.example.mydomain; libname prod server=srv.__5010; /* Send job completion email to interested parties */ java.util.Properties mailattrs = new java.util.Properties(); mailattrs.put("from", "SAS@JOB-DoNotReply"); mailattrs.put("host", "exchangesvr"); mailattrs.put( "subject", "Weekly Load Job Completed"); Hashtable recip = new Hashtable(2); recip.put(javax.mail.Message.RecipientType.TO, "janeway@voyager"); recip.put(javax.mail.Message.RecipientType.CC, "jluc@enterprise"); mailattrs.put("recipient", recip); mailattrs.put(JobStatus_html, mmsgs); sendmail.send(mailattrs, true); proc sql; create table values as select * from prod.capable where date() - rpt_date LT 360; create table choices as select distinct division, location, product from values; quit; data choices; n = _n_; set choices end=end; if end then call symput("numrpts", n); run; %let rpt = 0; %do %while &rpt le &numrpts ; proc sql; 3 NESUG 17 Applications select min(n), division, location, product into :rpt, :div, :loc, :prod from choices where n gt "&rpt"; quit; %mkcaprpts(values,&home/&div/&loc/&prod); %mkshewhart(values,&home/&div/&loc/&prod); %mkchoices(&home,&div,&loc,&prod); %end; %mend; where date() - rpt_date LT 360 <%=itemwhere%> <%=Where%> order by <%=field%> </nesug:text> <% Object[][] values = JUtil.SqlFetch( MrBean.JdbcLogin, ""+menu_sql); %> <nesug:text ref="MenuTitles" scope="session"> <td><%=MrBean.MkTitles(values)%></td> </nesug:text> mkshewhart macro: <nesug:text ref="MenuSelects" scope="session"> <td><%=MrBean.MkSelect(values)%></td> </nesug:text> <%}%> %macro mkshewhart(ds,gpath); filename boxfile "%trim(%left(&path))"; goptions reset=goptions gsfname=boxfile gsfmode=replace device=GIF; <HTML><BODY> <FORM NAME=’MenuSelects’> <!-- Code to display menu not shown --> </FORM> </BODY></HTML> /* Code to create sample limits from values dataset not shown here. */ proc shewhart data=values limits=limits gout=work.shew; XSCHART median * product / haxis=axis1 vaxis=axis2 /* rest of shewhart statements */ run; MenuItems.jspf: <jsp:useBean id="MrBean" class="example.nesug.MrBean" scope="session" /> <% proc greplay igout=work.shew nofs; replay shewhart; run; filename boxfile clear; quit; %mend; if (MrBean.MenuItems==null) { // Specify the menu. Object[][] MenuItemsA = { /*{Menu Label, db field name, sql where, */ {"Location", "location", null}, {"Division", "division", null}, {"Product", "product", "product>'AA0000' and product<='ZZ9999'"}, }; MrBean.MenuItems = MenuItemsA; } %> Due to its static and monolithic nature, the whole job is rerun whenever any erroneous data is reloaded. Therefore, it needs to be migrated to J2EE for dynamic generation. The following shows the migration done. Menu.jsp which calls MenuItems.jspf, creates a dynamic navigator for the user. Menu.jsp: <jsp:useBean id="MrBean" class="example.nesug.MrBean" scope="session" /> <%@taglib uri="/WEB-INF/nesug-tags.tld" prefix="nesug"%> <%@include file="JdbLogin.jspf"%> <%@include file="MenuItems.jspf"%> Menu.jsp, in conjunction with MenuItems.jspf, could be used as a general purpose menu builder reusable with other applications. <nesug:text id="MenuTitles" scope="session"/> <nesug:text id="MenuSelects" scope="session"/> <% String[][] Selection = MrBean.ResolveParameters (request, "SELECT"); GetReport.jsp (not discussed here) allows users to choose one of many reports. Gradually, as each report is converted to dynamic generation, the relevant code generating the link in GetReport.jsp is updated to point to the new URL. This allows both J2EE jobs to run comparatively until we are satisfied that the J2EE job is as least as acceptable as the pre-generated pages. String param = null; String Where = MrBean.MkWhere(Selection); for (int i=0; i<MrBean.MenuItems.length; i++) { String label = ""+MrBean.MenuItems[i][0]; String field = ""+MrBean.MenuItems[i][1]; Object itemwhere = MrBean.MenuItems[i][2]; %> <nesug:text id="menu_sql" scope="request"> select distinct <%=field%> from prod.capable The discussion has shown a SAS script could be sectioned into a number of text objects and each section modified as desired. The author conjured this technique on encountering SAS scripts with portions better done with J2EE and other portions best left alone. The technique allows migration without breaking up the job into J2EE 4 NESUG 17 Applications and non-J2EE portions, hence, avoiding increase in dependencies. run; filename boxfile clear; quit; %filesucker(<%=tmpfile%>boxplot.gif); </nesug:text> Generating Graphs through SAS/Connect SAS/Connect or JDBC provides no means of transporting objects like graphs to the J2EE server. Except with the trick of breaking the GIF file up into streams of characters to be stored in a dataset. It is then pulled over to the J2EE server through JDBC and reconstituted by the JSP. The SQL to pull the dataset records containing the GIF stream: <nesug:text id="GetGraph_sql" scope="request"> select g, n from <%tmpname%> order by n </nesug:text> First, a new macro, FileSucker, is defined: %macro filesucker(gf); /* Macro filesz(not shown) is a %sysexec ls –l * to get size of &gf and sets value of &filesz. */ %filesz(&gf); %let fldlen = 100; filename g "&gf" recfm=f lrecl=&filesz; data <%tmpname%>; infile g TRUNCOVER ; informat g $char&fldlen..; format g $char&fldlen..; if (_n_-1) * &fldlen le &filesz; n = _n_; input g $char&fldlen.. @@; output; run; %mend; Run SAS/Connect on those text tags: <%@include file="MkSASConnect.jspf"%> <nesug:text ref="LoadWeekly_sas" id="LoadWeekly_sas" scope="request"/> Connect.submitLines(""+boxplot_sas); Connect.submitLines(""+LoadWeekly_sas); Execute the SQL to pull the GIF stream over to the JSP. Object[][] g = JUtil.SqlFetch(Connect.jdbcConnection, ""+GetGraph_sql); if (g==null || g.length==0) return; The GIF stream cannot include characters not originally in the GIF, otherwise, corrupting the stream. Since the text tag object reproduces CR/LF characters faithfully, care must be taken where the tag angle brackets are placed to avoid creating those characters. As in the following loop to reassemble the GIF stream: When supplied with an argument &gf as filename, it proceeds to break the file up to store it as dataset rows of &fldlen length. The body of MkShewHart macro is now part of MkShewHart.jsp. After GREPLAY, FileSucker is called to suck the file into a dataset. The JSP predetermines the name of the dataset into which the file is sucked. for (int i=0; i<g.length; i++) {%><allegro:text ref="gbuf" scope="session"><% =g[i][0]%></allegro:text><%} %> <jsp:forward page="ShowGif.jsp"/> The former MkShewHart macro in MkShewHart.jsp: <% String tmpname = MrBean.mktmpname(); String tmpfile = MrBean.tmpdir+'/'+tmpname; %> <nesug:text id="boxplot_sas" scope="request"> filename boxfile "<%=tmpfile%>boxplot.gif"; goptions reset=goptions gsfname=boxfile gsfmode=replace device=GIF; Similarly, the actual content file generated by a JSP and received by the browser is always riddled with blank lines. For that reason, ShowGif.jsp must be a one-line jsp. <%@taglib uri="/WEB-INF/allegro-tags.tld" prefix="allegro"%><allegro:text id="gbuf" ref="gbuf" scope="session"></allegro:text><% =gbuf%> /* code to create sample limits from values * dataset not shown here. */ proc shewhart data=values limits=limits gout=work.shew; XSCHART median * product / haxis=axis1 vaxis=axis2 /* rest of shewhart statements not shown */ run; quit; ShowGif.jsp is called by MkShewHart.jsp to send the GIF stream to the browser. Similar intricacy is appropriate when constructing files to be viewed by Excel plug-in of the browser, as illustrated by ShowXls.jsp <%@page contentType="application/vnd.ms-excel"% ><%@taglib uri="/WEB-INF/allegro-tags.tld" proc greplay igout=work.shew nofs; replay shewhart; 5 NESUG 17 Applications prefix="allegro"%><allegro:text id="TabulateDataLines" ref="TabulateDataLines" scope="session"></allegro:text><% =TabulateDataLines%> from DICTIONARY.MEMBERS where libname='WORK'; quit; Where we could also exploit workdir for session transient file operations, for example, like FileSucker operations. The author plans to enhance the text tag classes to allow options to ignore CR/LF characters, to avoid needing such intricacies. Beyond the boundaries of a text object, a JSP would still generate an HTTP stream riddled with blank lines, any file stream like XLS or GIF should not be projected by the JSP that had generated it. Rather, the generating JSP should call the appropriate ShowXxx.jsp. Difference Between SAS/Share JDBC and SAS/Connect JDBC Use of JDBC from both SAS/Share and SAS/Connect has been shown in this discussion. Sharing a Macro Store SAS/Share JDBC is invoked using SAS/Share-Net JDBC driver. It retains the full resource sharing and locking features of SAS/Share. SAS macros used by J2EE dynamic reporting should be in a separate macro store from that used for running batch jobs, to keep the catalogue file as small as possible. SAS sessions cannot share a writeable catalogue file, as the first session would lock it until the session ends. It is also not recommended to declare the WORK macro catalogue with WRITE-ONLY access, as that would preclude JSPs from constructing macros dynamically. The contents of a dataset produced by SAS script submitted through SAS/Connect can be extracted to a servlet using SAS/Connect JDBC, without use of SOCKET or FTP FILENAMEs, or remote file systems. SAS/Connect does not have resource sharing capability. Therefore, SAS/Connect or its JDBC derivative should not be used for read/write on a shared production dataset. As shown in the examples, SAS scripts running on SAS/Connect should reference librefs of SAS/Share servers when using shared data. The following procedure is recommended. Say, the file name of the J2EE macro catalogue is j2ee.macr.sas7bcat. For each SAS/Connect session invoked by a JSP or servlet include this as example init script to copy j2ee.macr.sas7bcat into the WORK libref directory %let brother=where.art.thou; libname here server=brother.__5050; proc sql noprint; select max(path) into :workdir from DICTIONARY.MEMBERS where libname='WORK'; quit; SAS/Share JDBC is incapable of procedural and iterative supplementary languages like Sybase TSQL or Oracle PL/SQL. With the full power of SAS behind it, SAS/Connect provides superior equivalence to the supplementary languages of those RDBMSs. A JDBC connection derived from the SAS/Connect session that generated a session-transient object, such as a graph, is essential for sucking the object over. /* Copy the macro catalog to work directory * before any macros are called or created * which would lock work.sasmacr */ %sysexec cp / usr/sas/v8/macros/j2ee.macr.sas7bcat &workdir/sasmacr.sas7bcat; SAS/Connect JDBC is a telnet connection to a single user SAS session. The JDBC connection is elicted from a SAS/Connect connection simply: /* Trigger recognition of work.sasmacr as * default mac store by any macro reference. */ %let workdir = %trim(&workdir); libname macros "&workdir"; Object[][] HanSolo = JUtil.SqlFetch(Connect.jdbcConnection, ""+Princess_Leia_SQL); /* Define dataset libraries */ %let orbit1=obiwan.kenobi.jedi; %let orbit2=darth.vader.sith; libname skywalk remote server=orbit1.__20010; libname moonwalk remote server=orbit2.__20010; %annomac; Where Connect is the instance variable of the SAS/Connect session. A ShareNet JDBC connection connects to a multi-user SAS/Share process. Details of obtaining ShareNet JDBC connection is not explained but exemplified in calling Note extraction of workdir is helpful, proc sql noprint; select max(path) into :workdir 6 NESUG 17 Applications MrBean (also not shown) which initiates and stores the connection, Java charting in ADS pales in comparison to SAS/Graph with QC and STAT. Being a non-object-oriented drawing system however, compositing graphs using SAS/Graph overlay is a very delicate affair. Retaining SAS/Graph for statistical charting, JfreeChart with CEWolf appears as a good alternative to ADS APIs for business and composite charting. It may be wiser too to depend on dedicated IDEs like Jbuilder or NetBeans. Object[][] values = JUtil.SqlFetch( MrBean.JdbcLogin, ""+menu_sql); Caching the Reports It may be apparent that some dynamically generated reports require caching. Presuming that a caching strategy has been defined and a list of Most Frequently Used report signatures accumulated. Hence, the usefulness of RunUrl class when running a nightly scheduled J2EE job to trigger JSP reports falling into the signature list. Contacting the Author Author’s Name Mailing Address Email Comparison of SAS WebTechnologies SAS/Connect with Text Tag & FileSucker SAS/WebAF with IOM SAS/Intrnet Syloke J Soong 405 Western Ave #193 South Portland ME 04106 [email protected] Appendix – Initiating a SAS/Connect Session A SAS/Connect connection is a telnet conduit for a JSP to start and communicate with a SAS session on the server, with the following command: Submits a SAS script Y Y Y Produces SAS/Graph files Y Y Y SAS/Graph files streamed N N Y directly to JSP Need to register application Y N N Derivative JDBC connection N N1 Y SAS session per connection N N Y 1 The method deriving a JDBC connection method from an IOM connection does not work in AppDev Studio 2. AppDev Studio 3 allows JDBC Connection to be specified, which is not derived from the IOM connection. sas -dmr -comamid tcp -device grlink -noterminal -nosyntaxcheck ConnectionInfo class (instantiated by SASConnect.jspf) contains the telnet command: package example.sas.net.connect; public class ConnectInfo extends java.util.Properties { public ConnectInfo() {} Within SAS8.2 and AppDev Studio (ADS) 2, the author’s experience with IOM access on SAS/Share librefs has been unusably slow. Referencing SAS/Share librefs is necessary for dataset sharing. Providers of non-web EIS normally require extraction of data cubes from a shared system for non-shared use. As EIS cubes go by GByte sizes with latencies too long for ad-hoc generation, a J2EE installation would need to consider restricting concurrent use of an IOM dependent URL with the JSP responding, “The number of accesses has exceeded the number of cubes available. Please try later.” ADS3 provides widgets which accepts a JDBC connection. JDBC, unlike IOM, Intrnet or SAS/Connect cannot be used to trigger any AF or EIS widgets dependent on SCL. public void setDefaults() { put("prompt1", "login:"); put("prompt2", "Password:"); put("prompt3", "sasuser>"); put("promptTimeout1", "5"); put("promptTimeout2", "5"); put("promptTimeout3", "5"); put("sasPortTagTimeout", "45"); put("response1", "sasuser"); put("response2", "saspw"); put("response3", sasdmr); put("responseTimeout1", "30"); put("responseTimeout2", "30"); put("userNameResponse", "response1"); put("passwordResponse", "response2"); } String host; int port; static String sasdmr = "sas -dmr -comamid tcp -device grlink -noterminal -nosyntaxcheck"; } IOM is advantageous due to dependence on SAS Object Spawner, which exploits CORBA technology. An Object Spawner process waits listening for client requests. Starting a new SAS/Connect connection starts a whole new SAS session. SAS/Connect Spawner is merely SAS’s courtesy of yet another telnet server. 7 NESUG 17 Applications SASConnect.jspf (called by LoadWeekly.jsp) instantiates the classes performing the connection set-up: connection.clearListLines(); connection.rsubmit(lines); connection.clearEditLines(); lines = connection.getListLines(); if (lines.length()>0) list.add(lines); <%@page pageEncoding="UTF-8"%> <jsp:useBean id="Connect" class="example.sas.net.connect.SASConnection" scope="session"/> <%@page import= "example.sas.net.connect.ConnectionInfo"%> <% ConnectionInfo conninf = new ConnectionInfo(); conninf.host = "prodshr"; conninf.port = 23; conninf.setDefaults(); Connect.setConnectionInfo(conninf); Connect.connect(); %> } status.add("Execution complete."); } catch (ConnectException e) { status.add(e.getMessage()); } } public synchronized void connect() { try { status.add("Logging onto " + host); connection = (ConnectClient) new TelnetConnectClient(connectInfo); connection.connect( connectInfo.host, connectInfo.port); jdbcConnection = connection.getSharenet(); System.out.println(jdbcConnection); connect = true; status.add( "SAS init, connection complete"); } catch (ConnectException e) { status.add(e.getMessage()); System.out.println(e); } return; } SASConnect.jspf passes ConnectionInfo properties to SASConnection class. The SASConnection class reads the telnet command from the properties to perform the telnet. The SASConnection class is a modification of an example from SAS Support web site. Tunneled connection, a requirement for connection in applets, is not in the modified code. SASConnection.java: package example.sas.net.connect; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; import java.util.Vector; import com.sas.net.connect.ConnectClient; import com.sas.net.connect.ConnectException; import com.sas.net.connect.TelnetConnectClient; public void disconnect() { if (connect == true) { try { connection.disconnect(); connect = false; } catch (ConnectException e) { connect = false; } connection = null; } } public class SASConnection { public SASConnection() {} public SASConnection( ConnectionInfo connectinfo) { connectInfo = connectinfo; } public SASConnection setConnectionInfo( ConnectionInfo connectinfo) { connectInfo = connectinfo; return this; } boolean connect = false; String host = null; ConnectionInfo connectInfo; public synchronized void submitLines(String lines) { try { if ((lines!=null)&&(lines.length()>0)) { status.add( "Submitting SAS statements ..."); public int port = 23; public java.sql.Connection jdbcConnection = null; public ConnectClient connection = null; public Vector status = new Vector(); public Vector list = new Vector(); } 8 NESUG 17 Applications Appendix – RunUrl URL url = new URL(args[0]); URLConnection uconn = url.openConnection(); Object ucont = uconn.getContent(); System.out.println("Content:"+ucont); RunUrl.java: package example.net; } catch (MalformedURLException e) { e.printStackTrace(); } catch (java.io.IOException e) { e.printStackTrace(); } import java.net.URL; import java.net.URLConnection; import java.net.MalformedURLException; public class RunUrl { public RunUrl(){} } public static void main(String[] args) { try{ } Appendix - Remote SAS/Graph from JSP through SAS/Connect Browser HTTP Request HTTP Response Tomcat Embedded SAS script JSP Telnet SAS script executes goption GIF GIF displayed File Stream Reassembler JDBC Dataset containing GIF Stream sas -dmr FileSucker macro GIF File SAS Server SAS® and all other SAS Institute Inc. product or service names are registered trademarks or trademarks of SAS Institute Inc. in the USA and other countries. ® indicates USA registration. Other brand and product names are registered trademarks or trademarks of their respective companies. 9