/* * Copyright (C) 2011 Laurent Caillette * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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/>. */ package org.novelang.nhovestone.driver; import java.io.IOException; import java.net.ServerSocket; import java.util.concurrent.TimeUnit; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import org.novelang.configuration.parse.DaemonParameters; import org.novelang.daemon.HttpDaemon; import org.novelang.logger.Logger; import org.novelang.logger.LoggerFactory; import org.novelang.outfit.Husk; import org.novelang.outfit.shell.ProcessCreationException; import org.novelang.outfit.shell.ProcessInitializationException; /** * Starts and stops an {@link org.novelang.daemon.HttpDaemon} in its deployment directory, * in a separate JVM. * * @author Laurent Caillette */ public class HttpDaemonDriver extends EngineDriver { private static final Logger LOGGER = LoggerFactory.getLogger( HttpDaemonDriver.class ); private final int tcpPort ; public HttpDaemonDriver( final Configuration configuration ) { super( enrichWithProgramArguments( configuration ), HttpDaemon.COMMAND_NAME, PROCESS_STARTED_SENSOR ) ; // Could avoid this check either by some complicated inheritance that Husk doesn't // support for now, or by adding a constructor parameter in the superclass. Preconditions.checkArgument( configuration.getProgramOtherOptions() == null, "Use method in " + Configuration.class.getName() + " to set program options" ) ; this.tcpPort = getTcpPortForSure( configuration ) ; } private static int getTcpPortForSure( final Configuration configuration ) { final Integer tcpPort = configuration.getHttpPort() ; if( tcpPort == null ) { return org.novelang.configuration.ConfigurationTools.DEFAULT_HTTP_DAEMON_PORT ; } else { return tcpPort ; } } private static EngineDriver.Configuration enrichWithProgramArguments( final Configuration configuration ) { return configuration.withProgramOtherOptions( "--" + DaemonParameters.OPTIONNAME_HTTPDAEMON_PORT, "" + getTcpPortForSure( configuration ) ) ; } public int getTcpPort() { return tcpPort ; } @Override public void start( final long timeout, final TimeUnit timeUnit ) throws IOException, InterruptedException, ProcessCreationException, ProcessInitializationException { ensureTcpPortAvailable() ; super.start( timeout, timeUnit ); } @SuppressWarnings( { "SocketOpenedButNotSafelyClosed" } ) public void ensureTcpPortAvailable() throws IOException { final ServerSocket serverSocket; try { serverSocket = new ServerSocket( tcpPort ); } catch( IOException e ) { // Need to do this because some finally clause in calling class may cause an exception // masking this one. LOGGER.error( "Port already in use: ", tcpPort ) ; throw e ; } serverSocket.close() ; } private static final Predicate< String > PROCESS_STARTED_SENSOR = new Predicate< String >() { @Override public boolean apply( final String lineInConsole ) { // Can't use "Server started" because this message is never flushed to the standard output // in time. // return lineInConsole.contains( "Server started " ) ; return lineInConsole.contains( "Starting novelang.daemon.HttpDaemon" ) ; } } ; @Husk.Converter( converterClass = ConfigurationHelper.class ) public interface Configuration extends EngineDriver.Configuration< Configuration > { Integer getHttpPort() ; Configuration withHttpPort( Integer port ) ; } }