/* * #%L * gitools-ui-app * %% * Copyright (C) 2013 Universitat Pompeu Fabra - Biomedical Genomics group * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ package org.gitools.ui.app.batch; import org.apache.log4j.Logger; import org.gitools.ui.app.batch.tools.VersionTool; import org.gitools.ui.core.Application; import java.awt.*; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.net.URLDecoder; import java.nio.channels.ClosedByInterruptException; import java.util.HashMap; import java.util.Map; public class CommandListener implements Runnable { private static final Logger log = Logger.getLogger(CommandListener.class); private static CommandListener listener; private int port = -1; private ServerSocket serverSocket = null; private Socket clientSocket = null; private final Thread listenerThread; private boolean halt = false; public static synchronized void start(int port) { start(port, null); } public static synchronized void start(int port, String[] args) { if (args != null && args.length > 0) { if (checkSameVersion(port)) { // Pass the arguments to the other gitools instance Socket socket = null; try { System.out.println("Sending commands to running Gitools application"); socket = new Socket("localhost", port); PrintWriter out = new PrintWriter(socket.getOutputStream(), true); InputStream in = socket.getInputStream(); StringBuilder cmd = new StringBuilder(); for (String arg : args) { cmd.append(arg).append(' '); } out.println(cmd.toString()); // Wait server response while (in.available() == 0) { try { Thread.sleep(1000); } catch (Exception ee) { } } // Print command output BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String responseLine; while (socket.isConnected() && ((responseLine = reader.readLine()) != null)) { System.out.println(responseLine); } } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } // Close gitools System.exit(0); } } System.out.println("Starting command listener on port " + port); listener = new CommandListener(port); listener.listenerThread.start(); } public static synchronized void halt() { if (listener != null) { listener.halt = true; listener.listenerThread.interrupt(); listener.closeSockets(); listener = null; } } private CommandListener(int port) { this.port = port; listenerThread = new Thread(this); } /** * Loop forever, processing client requests synchronously. The server is single threaded. */ public void run() { CommandExecutor cmdExe = new CommandExecutor(); try { serverSocket = new ServerSocket(port); log.info("Listening on port " + port); while (!halt) { clientSocket = serverSocket.accept(); processClientSession(cmdExe); if (clientSocket != null) { try { clientSocket.close(); clientSocket = null; } catch (IOException e) { log.error("Error in client socket loop", e); } } } } catch (java.net.BindException e) { log.error(e); } catch (ClosedByInterruptException e) { log.error(e); } catch (IOException e) { if (!halt) { log.error("IO Error on port socket ", e); } } } /** * Process a client session. Loop continuously until client sends the "halt" message, or closes the connection. * * @param cmdExe * @throws IOException */ private void processClientSession(CommandExecutor cmdExe) throws IOException { PrintWriter out = null; BufferedReader in = null; try { out = new PrintWriter(clientSocket.getOutputStream(), true); in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); String inputLine; while (!halt && (inputLine = in.readLine()) != null) { String cmd = inputLine; if (cmd.startsWith("GET")) { String command = null; Map<String, String> params = null; String[] tokens = inputLine.split(" "); if (tokens.length < 2) { sendHTTPResponse(out, "ERROR unexpected command line: " + inputLine); return; } else { String[] parts = tokens[1].split("\\?"); if (parts.length < 2) { sendHTTPResponse(out, "ERROR unexpected command line: " + inputLine); return; } else { command = parts[0]; params = parseParameters(parts[1]); } } // Consume the remainder of the request, if any. This is important to free the connection. String nextLine = in.readLine(); while (nextLine != null && nextLine.length() > 0) { nextLine = in.readLine(); } // If a callback (javascript) function is specified write it back immediately. This function // is used to cancel a timeout handler String callback = params.get("callback"); if (callback != null) { sendHTTPResponse(out, callback); } StringWriter response = new StringWriter(); processGet(command, params, cmdExe, new PrintWriter(response)); // If no callback was specified write back a "no response" header if (callback == null) { sendHTTPResponse(out, response.toString()); } // http sockets are used for one request onle return; } else { // Port command cmdExe.execute(inputLine.split(" "), out); return; } } } catch (IOException e) { log.error("Error processing client session", e); } finally { if (out != null) { out.close(); } if (in != null) { in.close(); } } } private static final String CRNL = "\r\n"; private static final String CONTENT_TYPE = "Content-Type: "; private static final String HTTP_RESPONSE = "HTTP/1.1 200 OK"; private static final String HTTP_NO_RESPONSE = "HTTP/1.1 204 No Response"; private static final String CONTENT_LENGTH = "Content-Length: "; private static final String CONTENT_TYPE_TEXT_HTML = "text/html"; private static final String CONNECTION_CLOSE = "Connection: close"; private static void sendHTTPResponse(PrintWriter out, String result) { out.println(result == null ? HTTP_NO_RESPONSE : HTTP_RESPONSE); if (result != null) { out.print(CONTENT_TYPE + CONTENT_TYPE_TEXT_HTML); out.print(CRNL); out.print(CONTENT_LENGTH + (result.length())); out.print(CRNL); out.print(CONNECTION_CLOSE); out.print(CRNL); out.print(CRNL); out.print(result); out.print(CRNL); } out.close(); } private void closeSockets() { if (clientSocket != null) { try { clientSocket.close(); clientSocket = null; } catch (IOException e) { log.error("Error closing clientSocket", e); } } if (serverSocket != null) { try { serverSocket.close(); serverSocket = null; } catch (IOException e) { log.error("Error closing server socket", e); } } } /** * Process an http get request. */ private void processGet(String command, Map<String, String> params, CommandExecutor cmdExe, PrintWriter out) throws IOException { final Frame mainFrame = Application.get(); // Trick to force window to front, the setAlwaysOnTop works on a Mac, toFront() does nothing. mainFrame.toFront(); mainFrame.setAlwaysOnTop(true); mainFrame.setAlwaysOnTop(false); if (command.equals("/load")) { String file = params.get("file"); if (file != null) { cmdExe.execute(new String[]{"load", file}, out); } else { out.println("ERROR Parameter \"file\" is required"); } } else { out.println("ERROR Unknown command: " + command); } out.flush(); } /** * Parse the html parameter string into a set of key-value pairs. Parameter values are * url decoded with the exception of the "locus" parameter. * * @param parameterString * @return */ private Map<String, String> parseParameters(String parameterString) { // Do a partial decoding now (ampersands only) parameterString = parameterString.replace("&", "&"); HashMap<String, String> params = new HashMap(); String[] kvPairs = parameterString.split("&"); for (String kvString : kvPairs) { // Split on the first "=", all others are part of the parameter value String[] kv = kvString.split("=", 2); if (kv.length == 1) { params.put(kv[0], null); } else { String key = decodeURL(kv[0]); String value = decodeURL(kv[1]); params.put(kv[0], value); } } return params; } /** * Decode according to UTF-8. In the extremely unlikely * event that we are running on a platform which does not * support UTF-8 (it's part of the Java spec), URLDecoder.decode * is used. * * @param s * @return */ private static String decodeURL(String s) { if (s == null) { return null; } try { return URLDecoder.decode(s, "UTF-8"); } catch (UnsupportedEncodingException e) { return URLDecoder.decode(s); } } /** * Check if the current open instance is the same version * * @return True if there are the same version, false if not the same version or if there is no other instance running. */ private static boolean checkSameVersion(int port) { Socket socket = null; PrintWriter out = null; BufferedReader in = null; try { socket = new Socket("localhost", port); out = new PrintWriter(socket.getOutputStream(), true); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); VersionTool version = new VersionTool(); out.println(version.getName()); String otherInstanceVersion = in.readLine(); in.close(); out.close(); if (otherInstanceVersion != null && otherInstanceVersion.equals(version.getVersion())) { return true; } return false; } catch (IOException e) { return false; } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); System.exit(1); } } } } }