SLink升级到能支持Eclipse 3.4.1(Ganymede)的版本,虽然改动SLink的代码较多,但都不算是核心的修改,充其量只是给她穿衣洗脸梳头抹胭脂。
如果把SLink比作西施,那么现在我就要把病歪歪的西子做个换心术,彻底根治她的先天性心脏病(当然,这样西施就不是西施了)。
之所以说SLink病若西子,是因为她还不够powerful,目前SLink还不支持远程SAS编程,只能在SAS服务器上用插了SLink的Eclipse来写SAS代码。
改变能改变的,接受不能改变的。SLink的这个局限性我无法接受,我要改变她。
做换心术之前最重要的就是找到她的心。这个不难,只须循着她血液的流向(跟随代码的走向)就能找到。西子之心在这儿:
com.anaxima.slink.server.internal.ServerConnection
原作者Thomas Vater 的代码风格很好,可读性很强。现在我们可以观察一下西子之心:
/* * $Id: ServerConnection.java,v 1.1 2005/12/09 15:58:28 tv Exp $ * Copyright 2005 anaxima GmbH, Germany - All Rights Reserved. * * This software is the proprietary information of * anaxima GmbH, Germany. Use is subject to license terms. */ package com.anaxima.slink.server.internal; import java.io.IOException; import java.io.PrintStream; import java.net.InetSocketAddress; import java.net.Socket; import java.text.DecimalFormat; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import com.anaxima.slink.server.ServerPlugin; import com.anaxima.slink.tools.LogHelper; import com.anaxima.slink.tools.log.ILogger; /** * Make a connection to a SAS Server. * * @author Thomas Vater */ public class ServerConnection { // log object private static final ILogger log = ServerPlugin.getLogger(); /** * Format for ports. */ private static final DecimalFormat PORTFMT = new DecimalFormat("00000"); /** * Connect retries before failure. */ private static final int CONNECT_RETRY = 20; /** * Connect wait before next retry. */ private static final int CONNECT_WAIT = 5000; /** * Host name to connect to. */ private String _host; /** * Port number to connect with. */ private int _port; /** * Start port number for result connection. */ private int _startResultPort; /** * Maximum port number for result connection. */ private int _endResultPort; /** * Current result port. */ private int _resultPort; /** * SLink server communication socket. */ private static Socket _sasSocket; /** * SLink server communication print stream. */ private PrintStream _out; /** * Result data collector. */ private ResultCollector _resultCollector; /** * Log data collector. */ private ResultCollector _logCollector; /** * Output data collector. */ private ResultCollector _printCollector; public ServerConnection() {} /** * Creates a new server connection object for * * @param argHost; * @param argPort; */ public ServerConnection(String argHost, int argPort, int argStartResultPort, int argEndResultPort) { _host = argHost; _port = argPort; _startResultPort = argStartResultPort; _endResultPort = argEndResultPort; _resultPort = argStartResultPort; // set up connection to SLink server int retry = 0; while (retry < CONNECT_RETRY && _out == null) { try { _sasSocket = new Socket(); if (log.isDebugEnabled()) log.debug("Trying to connect " + _host + ":" + _port + "..."); _sasSocket.connect(new InetSocketAddress(_host, _port), 0); _out = new PrintStream(_sasSocket.getOutputStream()); } catch (IOException ioe) { if (log.isDebugEnabled()) log.debug(ServerPlugin.getMessage("slinkserver.connection.error.backend", new Integer(retry)), ioe); _sasSocket = null; _out = null; try { Thread.sleep(CONNECT_WAIT); } catch (InterruptedException ir) { // ignore; } retry++; } } // TODO: Notify user if connection to SLink backend failed if (_out == null) { log.error(ServerPlugin.getMessage("slinkserver.connection.error.backend.final")); } } /** * Format port numer into 5 digits. * @param argPortNo * @return Formatet port number. */ private String _formatPort(int argPortNo) { return PORTFMT.format(argPortNo); } /** * Returns the next result port. This method implements a round robin * distribution of ports. * * @return Next result port. */ private int _nextPort() { int next = _resultPort++; if (_resultPort >= _endResultPort) _resultPort = _startResultPort; return next; } /** * Evaluate results. */ private boolean _evaluateResults(int argResultPort) { boolean retOk = _resultCollector != null && !_resultCollector.isAlive() && !_resultCollector.hasError(); boolean logOk = _logCollector != null && !_logCollector.isAlive() && !_logCollector.hasError(); boolean prnOk = _printCollector != null && !_printCollector.isAlive() && !_printCollector.hasError(); if (!retOk) { System.out.println("RET result FAILED."); if (_resultCollector != null) System.out.println(_resultCollector.getException()); } if (argResultPort == 99999) return retOk; if (!logOk) { System.out.println("LOG result FAILED."); if (_logCollector != null) System.out.println(_logCollector.getException()); } if (!prnOk) { System.out.println("PRINT result FAILED."); if (_printCollector != null) System.out.println(_printCollector.getException()); } return (retOk && logOk && prnOk); } /** * Send a command to the server and returns the result. * * @param argCmd * Command to execute. * * @param args * Array of arguments. May be <code>null</code>. * * @return <code>true</code> if all results were successfully collected. <code>false</code> otherwise. */ public boolean sendCommand(String argCmd, String[] args) throws CoreException { int resultPort; int logPort; int printPort; try { if ("SHUTDOWN".equalsIgnoreCase(argCmd)) { resultPort = 99999; logPort = 99999; printPort = 99999; } else { resultPort = _nextPort(); logPort = _nextPort(); printPort = _nextPort(); // Code added by Sam Chen on 12/12/2008 10:24 ==> if (log.isDebugEnabled()) { log.debug("resultPort: " + resultPort + "; logPort: " + logPort + "; printPort: " + printPort); } // Code added by Sam Chen on 12/12/2008 10:24 <== } // send ports: resultPort, logPort and printPort _out.println(_formatPort(resultPort)); _out.println(_formatPort(logPort)); _out.println(_formatPort(printPort)); // send command _out.println(argCmd.toUpperCase()); // send number of args _out.println((args==null)?"0":""+args.length); // send arguments if (args != null) { for (int i=0; i<args.length; i++) { _out.println(args[i]); } } // send SYNC _out.println("SYNC"); _out.flush(); if (resultPort != 99999) { // setup result collectors _resultCollector = new ResultCollector("RET", resultPort); _logCollector = new ResultCollector("LOG", logPort); _printCollector = new ResultCollector("OUT", printPort); // start them all _resultCollector.start(); _logCollector.start(); _printCollector.start(); // wait collectors to finish thier jobs try { _logCollector.join(); _printCollector.join(); _resultCollector.join(); } catch (InterruptedException ie) { // ignore } } } catch (Exception e) { throw new CoreException(new Status(IStatus.ERROR, ServerPlugin.PLUGIN_ID, IStatus.OK, ServerPlugin.getMessage("slinkserver.connection.error.cmd"), e)); } return _evaluateResults(resultPort); } /** * Sends a shutdown command to the server. */ public void doShutdown() throws CoreException { // send shutdown command sendCommand("SHUTDOWN", (String[])null); // close down socket try { if (_sasSocket != null) _sasSocket.close(); if (_out != null) _out.close(); } catch (IOException ioe) { throw new CoreException(new Status(IStatus.ERROR, ServerPlugin.PLUGIN_ID, IStatus.OK, ServerPlugin.getMessage("slinkserver.connection.error.shutdown"), ioe)); } _sasSocket = null; } /** * Returns the result data as String. */ public String getResult() { if (_resultCollector == null) return null; return _resultCollector.getResultAsString(); } /** * Returns the log data as String. */ public String getLog() { if (_logCollector == null) return null; // we will skip the first and last three lines // as they contain the log output of the PROC PRINT // log redirection String logText = _logCollector.getResultAsString(3, 3); // finally we filter out doubled lines return LogHelper.filter(logText); } /** * Returns the output data as String. */ public String getOutput() { if (_printCollector == null) return null; return _printCollector.getResultAsString(); } }
private的域和方法我们不用管,只要看public的,这些东西在我们研读之列:构造器, 方法sendCommand, doShutdown, getResult, getLog, getOutput
好,西子之心解读完毕。给她准备更换用的心脏:
package com.anaxima.slink.server.internal; import org.eclipse.core.runtime.CoreException; import com.anaxima.slink.server.ServerPlugin; import com.anaxima.slink.tools.log.ILogger; /** * Make a connection to a SAS Server. * * @author Thomas Vater * @author Sam Chen * @version 1.0 2008/12/15 15:38:28 */ public class RemoteServerConnection extends ServerConnection { // log object private static final ILogger log = ServerPlugin.getLogger(); /** * Creates a new server connection object for * * @param argHost; * @param argPort; */ public RemoteServerConnection() { super(); boolean connected = SLinkWebServicesClient.connect(); if (log.isDebugEnabled()) { log.debug(connected ? "Connected to remote server." : "Failed to connect to remote server."); } } /** * Send a command to the server and returns the result. * * @param argCmd * Command to execute. * * @param args * Array of arguments. May be <code>null</code>. * * @return <code>true</code> if all results were successfully collected. <code>false</code> otherwise. */ public boolean sendCommand(String argCmd, String[] args) throws CoreException { return SLinkWebServicesClient.sendCommand(argCmd, args); } /** * Sends a shutdown command to the server. */ public void doShutdown() throws CoreException { SLinkWebServicesClient.doShutdown(); } /** * Returns the result data as String. */ public String getResult() { return SLinkWebServicesClient.getResult(); } /** * Returns the log data as String. */ public String getLog() { return SLinkWebServicesClient.getLog(); } /** * Returns the output data as String. */ public String getOutput() { return SLinkWebServicesClient.getOutput(); } }
这颗心不同于西子的那颗多愁善感的心,它显得非常轻巧,无忧无虑,把活儿都交给SLinkWebServicesClient干。
SLinkWebServicesClient是何许人也?是我专门为新心而造的,他长得也比较清爽:
package com.anaxima.slink.server.internal; import java.net.MalformedURLException; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.codehaus.xfire.annotations.AnnotationServiceFactory; import org.codehaus.xfire.client.XFireProxyFactory; import org.codehaus.xfire.service.Service; import org.eclipse.jface.preference.IPreferenceStore; import com.anaxima.slink.server.IServerPluginConstants; import com.anaxima.slink.server.ServerPlugin; import com.grs.slink.webservices.ISLinkService; import com.grs.slink.webservices.SLinkServiceImpl; /** * SLink Web Services Client * * @author Sam Chen * @version 1.0 12/15/2008 14:44:29 */ public class SLinkWebServicesClient { public static String getSLinkWebServicesUrl() { // this supports hot swapping IPreferenceStore store = ServerPlugin.getPlugin().getPreferenceStore(); return store.getString(IServerPluginConstants.PREFKEY_SLINKWEBSERVICESURL); } private static ISLinkService getSLinkService() { ISLinkService service = null; Service serviceModel = new AnnotationServiceFactory().create(SLinkServiceImpl.class); try { service = (ISLinkService) new XFireProxyFactory().create(serviceModel, getSLinkWebServicesUrl()); } catch (MalformedURLException e) { e.printStackTrace(); } return service; } private static Log log = LogFactory.getLog(SLinkWebServicesClient.class); private SLinkWebServicesClient(){} public static boolean connect() { return getSLinkService().connect(); } public static boolean sendCommand(String argCmd, String[] args) { return getSLinkService().sendCommand(argCmd, args); } public static void doShutdown() { getSLinkService().doShutdown(); } public static String getResult() { return getSLinkService().getResult(); } public static String getLog() { return getSLinkService().getLog(); } public static String getOutput() { return getSLinkService().getOutput(); } public static String createFile(List<String> lines) { return getSLinkService().createFile(lines); } }
都是些不干重活的,那真正的重活谁干呢?你说对了:既然这儿有个WebServices的Client - SLinkWebServicesClient,肯定背后有个Web Service!
这是他的档案:
package com.grs.slink.webservices; import java.util.List; import javax.jws.WebService; @WebService public interface ISLinkService { public boolean connect(); public void doShutdown(); public boolean sendCommand(String argCmd, String[] args); public String getResult(); public String getLog(); public String getOutput(); public String createFile(List<String> lines); }
让我们来看他的庐山真面:
package com.grs.slink.webservices; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; import java.net.InetSocketAddress; import java.net.Socket; import java.text.DecimalFormat; import java.util.Iterator; import java.util.List; import javax.jws.WebService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.anaxima.slink.server.internal.ResultCollector; import com.anaxima.slink.tools.LogHelper; @WebService( serviceName = "SLinkService", endpointInterface = "com.grs.slink.webservices.ISLinkService" ) /** * SLink service implementation * * @author Sam Chen * @version 1.0 12/15/2008 13:33 */ public class SLinkServiceImpl implements ISLinkService { private static boolean connected; /** * Format for ports. */ private static final DecimalFormat PORTFMT = new DecimalFormat("00000"); /** * Connect retries before failure. */ private static final int CONNECT_RETRY = 20; /** * Connect wait before next retry. */ private static final int CONNECT_WAIT = 5000; /** * Host name to connect to. */ private String _host = "localhost"; /** * Port number to connect with. */ private int _port = 5999; public void setPort(int port) { _port = port; } /** * Start port number for result connection. */ private int _startResultPort = 20000; public void setStartResultPort(int startResultPort) { _startResultPort = startResultPort; } /** * Maximum port number for result connection. */ private int _endResultPort = 29999; public void setEndResultPort(int endResultPort) { _endResultPort = endResultPort; } /** * Current result port. */ private int _resultPort; /** * SLink server communication socket. */ private static Socket _sasSocket; /** * SLink server communication print stream. */ private PrintStream _out; /** * Result data collector. */ private ResultCollector _resultCollector; /** * Log data collector. */ private ResultCollector _logCollector; /** * Output data collector. */ private ResultCollector _printCollector; private static final Log log = LogFactory.getLog(SLinkServiceImpl.class); /** * Format port numer into 5 digits. * @param argPortNo * @return Formatet port number. */ private String _formatPort(int argPortNo) { return PORTFMT.format(argPortNo); } /** * Returns the next result port. This method implements a round robin * distribution of ports. * * @return Next result port. */ private int _nextPort() { int next = _resultPort++; if (_resultPort >= _endResultPort) _resultPort = _startResultPort; return next; } /** * Evaluate results. */ private boolean _evaluateResults(int argResultPort) { boolean retOk = _resultCollector != null && !_resultCollector.isAlive() && !_resultCollector.hasError(); boolean logOk = _logCollector != null && !_logCollector.isAlive() && !_logCollector.hasError(); boolean prnOk = _printCollector != null && !_printCollector.isAlive() && !_printCollector.hasError(); if (!retOk) { System.out.println("RET result FAILED."); if (_resultCollector != null) System.out.println(_resultCollector.getException()); } if (argResultPort == 99999) return retOk; if (!logOk) { System.out.println("LOG result FAILED."); if (_logCollector != null) System.out.println(_logCollector.getException()); } if (!prnOk) { System.out.println("PRINT result FAILED."); if (_printCollector != null) System.out.println(_printCollector.getException()); } return (retOk && logOk && prnOk); } public boolean connect() { if (this.connected) { return true; } _resultPort = _startResultPort; // set up connection to SLink server int retry = 0; while (retry < CONNECT_RETRY && _out == null) { try { _sasSocket = new Socket(); if (log.isInfoEnabled()) { log.info("Trying to connect " + _host + ":" + _port + "..."); } _sasSocket.connect(new InetSocketAddress(_host, _port), 0); _out = new PrintStream(_sasSocket.getOutputStream()); } catch (IOException ioe) { if (log.isDebugEnabled()) { log.debug("slinkserver.connection.error.backend", ioe); } _sasSocket = null; _out = null; try { Thread.sleep(CONNECT_WAIT); } catch (InterruptedException ir) { // ignore; } retry++; } } // TODO: Notify user if connection to SLink backend failed if (_out == null) { log.error("slinkserver.connection.error.backend.final"); } else { if (log.isInfoEnabled()) { log.info("Connected to " + _host + ":" + _port + "."); } this.connected = true; } return this.connected; } public void doShutdown() { // send shutdown command sendCommand("SHUTDOWN", (String[])null); // close down socket try { if (_sasSocket != null) _sasSocket.close(); if (_out != null) _out.close(); } catch (IOException ioe) { throw new RuntimeException("slinkserver.connection.error.shutdown", ioe); } _sasSocket = null; } public String getLog() { if (_logCollector == null) return null; // we will skip the first and last three lines // as they contain the log output of the PROC PRINT // log redirection String logText = _logCollector.getResultAsString(3, 3); // finally we filter out doubled lines return LogHelper.filter(logText); } public String getOutput() { if (_printCollector == null) return null; return _printCollector.getResultAsString(); } public String getResult() { if (_resultCollector == null) return null; return _resultCollector.getResultAsString(); } public boolean sendCommand(String argCmd, String[] args) { int resultPort; int logPort; int printPort; try { if ("SHUTDOWN".equalsIgnoreCase(argCmd)) { resultPort = 99999; logPort = 99999; printPort = 99999; } else { resultPort = _nextPort(); logPort = _nextPort(); printPort = _nextPort(); // Code added by Sam Chen on 12/12/2008 10:24 ==> if (log.isInfoEnabled()) { log.info("resultPort: " + resultPort + "; logPort: " + logPort + "; printPort: " + printPort); } // Code added by Sam Chen on 12/12/2008 10:24 <== } // send ports: resultPort, logPort and printPort _out.println(_formatPort(resultPort)); _out.println(_formatPort(logPort)); _out.println(_formatPort(printPort)); // send command _out.println(argCmd.toUpperCase()); // send number of args _out.println((args==null)?"0":""+args.length); // send arguments if (args != null) { for (int i=0; i<args.length; i++) { _out.println(args[i]); } } // send SYNC _out.println("SYNC"); _out.flush(); if (resultPort != 99999) { // setup result collectors _resultCollector = new ResultCollector("RET", resultPort); _logCollector = new ResultCollector("LOG", logPort); _printCollector = new ResultCollector("OUT", printPort); // start them all _resultCollector.start(); _logCollector.start(); _printCollector.start(); // wait collectors to finish thier jobs try { _logCollector.join(); _printCollector.join(); _resultCollector.join(); } catch (InterruptedException ie) { // ignore } } } catch (Exception e) { throw new RuntimeException("slinkserver.connection.error.cmd", e); } return _evaluateResults(resultPort); } public String createFile(List<String> lines) { String ret = ""; try { File dest = File.createTempFile("sas_tmp_", ".sas"); PrintWriter pw = new PrintWriter(new FileWriter(dest)); for (Iterator<String> it = lines.iterator(); it.hasNext(); ) { pw.println(it.next()); } pw.flush(); pw.close(); log.info("SAS src code copied to " + dest.getAbsolutePath()); ret = dest.getAbsolutePath(); } catch (Exception x) { log.error("Error occurred creating temporary SAS src file."); } return ret; } }
我已经把西子脆弱的心里装的那些忧愁全部转移到了这里。(关于java webservices的开发,可参见拙作《Web Services应用实例 -- Java Web App远程调用SAS程序的解决方案》http://sam-ds-chen.iteye.com/blog/180905 )
在SAS服务器上部署好web services并启动之后,执行换心术最后一步:
把
_connection = new ServerConnection("localhost", _port, _portReplyStart, _portReplyEnd);
换成
_connection = new RemoteServerConnection();
编译,打包,覆盖旧插件,启动Ganymede... 终于可以在自己的机器上做SAS开发了。下班,回家。