Return-Path: Delivered-To: apmail-incubator-sling-commits-archive@minotaur.apache.org Received: (qmail 57804 invoked from network); 5 May 2009 09:55:33 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 5 May 2009 09:55:33 -0000 Received: (qmail 77194 invoked by uid 500); 5 May 2009 09:55:08 -0000 Delivered-To: apmail-incubator-sling-commits-archive@incubator.apache.org Received: (qmail 77160 invoked by uid 500); 5 May 2009 09:55:08 -0000 Mailing-List: contact sling-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: sling-dev@incubator.apache.org Delivered-To: mailing list sling-commits@incubator.apache.org Received: (qmail 77138 invoked by uid 99); 5 May 2009 09:55:08 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 05 May 2009 09:55:08 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 05 May 2009 09:54:58 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 3F3BE2388C95; Tue, 5 May 2009 09:54:37 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r771647 - in /incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad: app/ControlListener.java app/Main.java base/app/MainDelegate.java Date: Tue, 05 May 2009 09:54:37 -0000 To: sling-commits@incubator.apache.org From: fmeschbe@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20090505095437.3F3BE2388C95@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: fmeschbe Date: Tue May 5 09:54:36 2009 New Revision: 771647 URL: http://svn.apache.org/viewvc?rev=771647&view=rev Log: SLING-954 Add ControlListener class and start/stop/status support to Main class and add usage info for new command line arguments SLING-953 Small fix to command line parsing allowing a single dash as a parameter argument as in "-f -" Added: incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/app/ControlListener.java (with props) Modified: incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/app/Main.java incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/app/MainDelegate.java Added: incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/app/ControlListener.java URL: http://svn.apache.org/viewvc/incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/app/ControlListener.java?rev=771647&view=auto ============================================================================== --- incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/app/ControlListener.java (added) +++ incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/app/ControlListener.java Tue May 5 09:54:36 2009 @@ -0,0 +1,257 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.sling.launchpad.app; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.UnknownHostException; + +/** + * The ControlListener class is a helper class for the {@link Main} + * class to support in Sling standalone application process communication. This + * class implements the client and server sides of a TCP/IP based communication + * channel to control a running Sling application. + *

+ * The server side listens for commands on a configurable host and port &endash; + * localhost:63000 by default &endash; supporting the following + * commands: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
CommandDescription
statusRequest status information. Currently only OK is sent back. If no + * connection can be created to the server the client assumes Sling is not + * running.
stopRequests Sling to shutdown.
+ */ +class ControlListener implements Runnable { + + // command sent by the client to cause Sling to shutdown + static final String COMMAND_STOP = "stop"; + + // command sent by the client to check for the status of the server + static final String COMMAND_STATUS = "status"; + + // the response sent by the server if the command executed successfully + private static final String RESPONSE_OK = "OK"; + + // The default port to listen on and to connect to + private static final int DEFAULT_LISTEN_PORT = 63000; + + // The reference to the Main class to shutdown on request + private final Main slingMain; + + // The socket address used for control communication + private final SocketAddress socketAddress; + + /** + * Creates an instance of this control support class. + *

+ * The host (name or address) and port number of the socket is defined by + * the listenSpec parameter. This parameter is defined as + * [ host ":" ] port. If the parameter is empty or + * null it defaults to localhost:63000. If the host name + * is missing it defaults to localhost. + * + * @param slingMain The Main class reference. This is only required if this + * instance is used for the server side to listen for remote stop + * commands. Otherwise this argument may be null. + * @param listenSpec The specification for the host and port for the socket + * connection. See above for the format of this parameter. + */ + ControlListener(Main slingMain, String listenSpec) { + this.slingMain = slingMain; + this.socketAddress = getSocketAddress(listenSpec); + } + + /** + * Implements the server side of the control connection starting a thread + * listening on the host and port configured on setup of this instance. + */ + void listen() { + if (socketAddress != null) { + Thread listener = new Thread(this); + listener.setDaemon(true); + listener.setName("Sling Control Listener@" + socketAddress); + listener.start(); + } else { + Main.info("No socket address to listen to", null); + } + } + + /** + * Implements the client side of the control connection sending the command + * to shutdown Sling. + */ + void shutdownServer() { + sendCommand(COMMAND_STOP); + } + + /** + * Implements the client side of the control connection sending the command + * to check whether Sling is active. + */ + void statusServer() { + sendCommand(COMMAND_STATUS); + } + + // ---------- Runnable interface + + /** + * Implements the server thread receiving commands from clients and acting + * upon them. + */ + public void run() { + ServerSocket server = null; + try { + server = new ServerSocket(); + server.bind(socketAddress); + Main.info("Sling Control Server started", null); + } catch (IOException ioe) { + Main.error("Failed to start Sling Control Server", ioe); + return; + } + + try { + while (true) { + + Socket s = server.accept(); + try { + String command = readLine(s); + Main.info(s.getRemoteSocketAddress() + ">" + command, null); + + if (COMMAND_STOP.equals(command)) { + slingMain.shutdown(); + writeLine(s, RESPONSE_OK); + + Main.info("Sling shut down, exiting Java VM", null); + System.exit(0); + + } else if (COMMAND_STATUS.equals(command)) { + writeLine(s, RESPONSE_OK); + + } else { + writeLine(s, "ERR:" + command); + + } + } finally { + try { + s.close(); + } catch (IOException ignore) { + } + } + } + } catch (IOException ioe) { + Main.error("Failure reading from client", ioe); + } finally { + if (server != null) { + try { + server.close(); + } catch (IOException ignore) { + } + } + } + } + + // ---------- socket support + + private SocketAddress getSocketAddress(String listenSpec) { + try { + if (listenSpec != null) { + int colon = listenSpec.indexOf(':'); + if (colon < 0) { + return new InetSocketAddress(InetAddress.getLocalHost(), + Integer.parseInt(listenSpec)); + } + return new InetSocketAddress(listenSpec.substring(0, colon), + Integer.parseInt(listenSpec.substring(colon + 1))); + } + + return new InetSocketAddress(InetAddress.getLocalHost(), + DEFAULT_LISTEN_PORT); + } catch (NumberFormatException nfe) { + Main.error("Cannot parse port number from '" + listenSpec + "'", + null); + } catch (UnknownHostException uhe) { + Main.error("Unknown host in '" + listenSpec + "': " + + uhe.getMessage(), null); + } + + return null; + } + + private void sendCommand(String command) { + if (socketAddress != null) { + Socket socket = null; + try { + socket = new Socket(); + socket.connect(socketAddress); + writeLine(socket, command); + String result = readLine(socket); + Main.info("Sent '" + command + "' to " + socketAddress + ": " + + result, null); + } catch (ConnectException ce) { + Main.info("No Sling running at " + socketAddress, null); + } catch (IOException ioe) { + Main.error("Failed sending '" + command + "' to " + + socketAddress, ioe); + } finally { + if (socket != null) { + try { + socket.close(); + } catch (IOException ignore) { + } + } + } + } else { + Main.info("No socket address to send '" + command + "' to", null); + } + } + + private String readLine(Socket socket) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader( + socket.getInputStream(), "UTF-8")); + return br.readLine(); + } + + private void writeLine(Socket socket, String line) throws IOException { + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter( + socket.getOutputStream(), "UTF-8")); + bw.write(line); + bw.write("\r\n"); + bw.flush(); + } +} Propchange: incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/app/ControlListener.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/app/ControlListener.java ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Rev Url Modified: incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/app/Main.java URL: http://svn.apache.org/viewvc/incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/app/Main.java?rev=771647&r1=771646&r2=771647&view=diff ============================================================================== --- incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/app/Main.java (original) +++ incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/app/Main.java Tue May 5 09:54:36 2009 @@ -69,6 +69,9 @@ this.commandLineArgs = parseCommandLine(args); + // check for control commands (might exit) + doControlCommand(); + // sling.home from the command line or system properties, else default this.slingHome = getSlingHome(commandLineArgs); info("Starting Sling in " + slingHome, null); @@ -80,6 +83,25 @@ SharedConstants.DEFAULT_SLING_LAUNCHER_JAR)); } + // ---------- Shutdown support for control listener and shutdown hook + + void shutdown() { + // remove the shutdown hook, will fail if called from the + // shutdown hook itself. Otherwise this prevents shutdown + // from being called again + try { + Runtime.getRuntime().removeShutdownHook(this); + } catch (Throwable t) { + // don't care for problems removing the hook + } + + // now really shutdown sling + if (sling != null) { + info("Stopping Sling", null); + sling.stop(); + } + } + // ---------- Notifiable interface /** @@ -152,10 +174,7 @@ @Override public void run() { info("Java VM is shutting down", null); - if (sling != null) { - info("Stopping Sling", null); - sling.stop(); - } + shutdown(); } // ---------- internal @@ -246,7 +265,8 @@ commandLine.put(key, arg.substring(2)); } else { argc++; - if (argc < args.length && !args[argc].startsWith("-")) { + if (argc < args.length + && (args[argc].equals("-") || !args[argc].startsWith("-"))) { commandLine.put(key, args[argc]); } else { commandLine.put(key, key); @@ -312,12 +332,12 @@ // ---------- logging // emit an informational message to standard out - private static void info(String message, Throwable t) { + static void info(String message, Throwable t) { log(System.out, "*INFO*", message, t); } // emit an error message to standard err - private static void error(String message, Throwable t) { + static void error(String message, Throwable t) { log(System.err, "*ERROR*", message, t); } @@ -353,4 +373,22 @@ }); } } + + private void doControlCommand() { + String commandSocketSpec = commandLineArgs.get("j"); + if ("j".equals(commandSocketSpec)) { + commandSocketSpec = null; + } + + ControlListener sl = new ControlListener(this, commandSocketSpec); + if (commandLineArgs.remove(ControlListener.COMMAND_STOP) != null) { + sl.shutdownServer(); + System.exit(0); + } else if (commandLineArgs.remove(ControlListener.COMMAND_STATUS) != null) { + sl.statusServer(); + System.exit(0); + } else if (commandLineArgs.remove("start") != null) { + sl.listen(); + } + } } Modified: incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/app/MainDelegate.java URL: http://svn.apache.org/viewvc/incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/app/MainDelegate.java?rev=771647&r1=771646&r2=771647&view=diff ============================================================================== --- incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/app/MainDelegate.java (original) +++ incubator/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/app/MainDelegate.java Tue May 5 09:54:36 2009 @@ -296,8 +296,12 @@ log("usage: " + MainDelegate.class.getName() - + " [ -l loglevel ] [ -f logfile ] [ -c slinghome ] [ -a address ] [ -p port ] [ -h ]"); + + " [ start | stop | status ] [ -j adr ] [ -l loglevel ] [ -f logfile ] [ -c slinghome ] [ -a address ] [ -p port ] [ -h ]"); + log(" start listen for control connection (uses -j)"); + log(" stop terminate running Sling (uses -j)"); + log(" start check whether Sling is running (uses-j)"); + log(" -j adr host and port to use for control connection in the format '[host:]port' (default localhost:63000)"); log(" -l loglevel the initial loglevel (0..4, FATAL, ERROR, WARN, INFO, DEBUG)"); log(" -f logfile the log file, \"-\" for stdout (default logs/error.log)"); log(" -c slinghome the sling context directory (default sling)");