/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.motorolamobility.preflighting.internal.daemon; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.motorolamobility.preflighting.core.exception.PreflightingToolException; import com.motorolamobility.preflighting.core.validation.ValidationManager.InputParameter; import com.motorolamobility.preflighting.core.verbose.DebugVerboseOutputter; import com.motorolamobility.preflighting.core.verbose.DebugVerboseOutputter.VerboseLevel; import com.motorolamobility.preflighting.i18n.PreflightingNLS; import com.motorolamobility.preflighting.internal.PreflightingApplication; import com.motorolamobility.preflighting.internal.commandoutput.NullOutputStream; import com.motorolamobility.preflighting.internal.commandoutput.OutputterFactory; import com.motorolamobility.preflighting.internal.commandoutput.OutputterFactory.OutputType; import com.motorolamobility.preflighting.internal.help.printer.HelpPrinter; public class Daemon { final public static String STATUS_CONTROL_MESSAGE = "are_you_alive"; final public static String LISTENING_STATUS_MESSAGE = "i_am_up_and_running"; final public static Integer DEFAULT_PORT = 50000; final public static int BOUND_TIMEOUT = 2000; final private static int TEST_TIMEOUT = 1000; final private static String LOCALHOST = "127.0.0.1"; private final int serverPort; private final String sdkPath; private Thread daemonThread = null; private PrintStream debugStream = null; public static PrintStream nullStream = new PrintStream(new NullOutputStream()); public Daemon(int serverPort, String sdkPath) { // Default debug/error stream DebugVerboseOutputter.setStream(nullStream); OutputterFactory.getInstance().setDefaultOutputter(OutputType.XML); this.serverPort = serverPort; this.sdkPath = sdkPath; } class DaemonRunnable implements Runnable { Socket socket; private final Daemon daemon; private final String command; public DaemonRunnable(Socket socket, String command, Daemon daemon) { this.socket = socket; this.command = command; this.daemon = daemon; } /* * delete -sdk parameter from client * throws an exception if -daemon is found * add -sdk parameter that comes from daemon */ private String processParameters(String parameters, PrintStream stream) throws PreflightingToolException { String newParams = null; String regex = "((?!(\\s+-)).)*"; //$NON-NLS-1$ Pattern pat = Pattern.compile(regex); Matcher matcher = pat.matcher(parameters); // for each parameter part found, process it while (matcher.find()) { String parameterValues = parameters.substring(matcher.start(), matcher.end()); // remove -sdk coming from client if (parameterValues.trim().startsWith("-sdk")) //$NON-NLS-1$ { newParams = parameters.substring(0, matcher.start()); newParams = newParams + parameters.substring(matcher.end(), parameters.length()); } if (parameterValues.trim().startsWith("-daemon")) //$NON-NLS-1$ { throw new PreflightingToolException("Cannot start a daemon inside another."); //$NON-NLS-1$ } } if (newParams == null) { newParams = parameters; } //adding the sdk path passed to the daemon if ((newParams != null) && (daemon.getSdkPath() != null)) { newParams += " -" + (InputParameter.SDK_PATH.getAlias()) + " " + daemon.getSdkPath(); } return newParams; } public void run() { PrintStream stream = null; try { String parameters = command; stream = new PrintStream(socket.getOutputStream(), true); DebugVerboseOutputter.printVerboseMessage("New input: " + parameters, //$NON-NLS-1$ VerboseLevel.v0); if (parameters.equals(STATUS_CONTROL_MESSAGE)) { stream.println(LISTENING_STATUS_MESSAGE); } else if (parameters.equals("-list-checkers")) { HelpPrinter.printXMLCheckerList(stream); } else if (parameters.equals("-list-devices")) { HelpPrinter.printXMLDevicesList(stream); } else if (parameters.equals("-get-checkers-devices-specsMap-xml")) { HelpPrinter.printXMLDevicesCheckersSpecsMap(stream); } else { parameters = processParameters(parameters, stream); PreflightingApplication.validate(parameters, stream, daemon.getDebugStream()); } stream.flush(); } catch (Exception e) { DebugVerboseOutputter.printVerboseMessage(PreflightingNLS.Daemon_ValidationError + " " + socket.getRemoteSocketAddress() + ". " + e.getMessage(), VerboseLevel.v0); } finally { if (stream != null) { stream.close(); } if (socket != null) { try { socket.close(); } catch (IOException e) { // do nothing since the socket was closed here } } } } } private void fork(Socket socket, String command) { DaemonRunnable runnable = new DaemonRunnable(socket, command, this); Thread t = new Thread(runnable, "AppValidator - " + socket.getLocalPort()); //$NON-NLS-1$ t.start(); } public PrintStream getDebugStream() { return debugStream; } private void run() throws IOException { ServerSocket socket = null; try { socket = new ServerSocket(serverPort); while (true) { try { Socket conn = socket.accept(); String command = readCommand(conn); if (!command.equals("-quit")) { fork(conn, command); } else { break; } } catch (OutOfMemoryError err) { // log DebugVerboseOutputter.printVerboseMessage( "The application ran out of memory: " + err.getMessage(), //$NON-NLS-1$ VerboseLevel.v0); break; } // any other exception occured. continue catch (Exception e) { DebugVerboseOutputter.printVerboseMessage( "The validation instance failed to execute: " + e.getMessage(), //$NON-NLS-1$ VerboseLevel.v0); } } } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { // do nothing since the socket was closed here } } } System.out.println(PreflightingNLS.Daemon_Stopped); } private String readCommand(Socket socket) throws IOException { String command = ""; InputStream in = socket.getInputStream(); BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(in)); command = reader.readLine(); } finally { if (reader != null) { reader.close(); } } return command; } /** * Starts this daemon in a new thread */ public void startDaemon() { try { System.out.println(PreflightingNLS.Daemon_StartingStatusMessage); //that's the daemon thread daemonThread = new Thread() { @Override public void run() { try { Daemon.this.run(); } catch (Exception e) { DebugVerboseOutputter.printVerboseMessage( "Daemon aborted: " + e.getMessage(), //$NON-NLS-1$ VerboseLevel.v0); } } }; daemonThread.start(); } catch (Exception e) { DebugVerboseOutputter.printVerboseMessage(PreflightingNLS.Daemon_StartingErrorMessage + " " + e.getMessage(), VerboseLevel.v0); } } /** * Test daemon with a status control message. * @param daemonThread * @param serverPort * @throws IOException * @throws UnknownHostException * @throws InterruptedException * @throws Exception */ public boolean testDaemon() throws UnknownHostException, IOException, InterruptedException { boolean returnValue = false; if (daemonThread.isAlive()) { DebugVerboseOutputter.printVerboseMessage(PreflightingNLS.bind( PreflightingNLS.Daemon_TestDaemonStatusMessage, serverPort), VerboseLevel.v0); int i = 1; while (true) { Socket clientSocket = null; BufferedReader reader = null; try { clientSocket = new Socket(LOCALHOST, serverPort); OutputStream out = clientSocket.getOutputStream(); String aux = Daemon.STATUS_CONTROL_MESSAGE + "\n"; out.write(aux.getBytes()); out.flush(); InputStream in = clientSocket.getInputStream(); reader = new BufferedReader(new InputStreamReader(in)); String line = reader.readLine(); if (line.equals(Daemon.LISTENING_STATUS_MESSAGE)) { DebugVerboseOutputter.printVerboseMessage(PreflightingNLS.bind( PreflightingNLS.Daemon_TestDaemonSucceedTry, i), VerboseLevel.v0); DebugVerboseOutputter.printVerboseMessage(PreflightingNLS.bind( PreflightingNLS.Daemon_LinsteningMessage, serverPort), VerboseLevel.v0); returnValue = true; break; } DebugVerboseOutputter.printVerboseMessage( PreflightingNLS.bind(PreflightingNLS.Daemon_TestDaemonFailedTry, i++), VerboseLevel.v0); //wait before next try Thread.sleep(TEST_TIMEOUT); } finally { if (reader != null) { reader.close(); } if (clientSocket != null) { clientSocket.close(); } } } } return returnValue; } public void join() throws InterruptedException { daemonThread.join(); } public String getSdkPath() { return sdkPath; } /** * Turn on/off debug mode. If debug is true, debug messages will be printed to the system error stream. * If false, no message is printed. * * @param debug true for debug mode on */ public void setDebugOn(boolean debug) { debugStream = debug ? System.err : nullStream; DebugVerboseOutputter.setStream(debugStream); } }