《Eclipse插件门外汉如何搞掂插件升级》续集:为西子换心

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

 

 

 

 

你可能感兴趣的:(apache,eclipse,socket,webservice,IE)