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开发了。下班,回家。