package com.tinkerpop.rexster; import com.tinkerpop.rexster.protocol.EngineConfiguration; import com.tinkerpop.rexster.protocol.EngineController; import com.tinkerpop.rexster.server.HttpRexsterServer; import com.tinkerpop.rexster.server.RexProRexsterServer; import com.tinkerpop.rexster.server.RexsterApplication; import com.tinkerpop.rexster.server.RexsterCommandLine; import com.tinkerpop.rexster.server.RexsterProperties; import com.tinkerpop.rexster.server.RexsterServer; import com.tinkerpop.rexster.server.RexsterSettings; import com.tinkerpop.rexster.server.ShutdownManager; import com.tinkerpop.rexster.server.XmlRexsterApplication; import com.tinkerpop.rexster.server.metrics.ReporterConfig; import com.tinkerpop.rexster.util.JuliToLog4jHandler; import org.apache.commons.configuration.HierarchicalConfiguration; import org.apache.commons.configuration.XMLConfiguration; import org.apache.commons.io.IOUtils; import org.apache.commons.io.filefilter.FileFileFilter; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.monitor.FileAlterationMonitor; import org.apache.commons.io.monitor.FileAlterationObserver; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.BindException; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogManager; /** * Main class for initializing, starting and stopping Rexster. * * @author Marko A. Rodriguez (http://markorodriguez.com) * @author Stephen Mallette (http://stephen.genoprime.com) */ public class Application { private static final Logger logger = Logger.getLogger(Application.class); static { // try to load a log4j properties file in the root of REXSTER_HOME final File logConfigFile = new File("log4j.properties"); URL logConfigFileUrl; try { if (logConfigFile.exists()) { // this one exists in the root of REXSTER_HOME logConfigFileUrl = logConfigFile.toURI().toURL(); } else { // a custom one from a user doesn't exist so use the default one in the jar logConfigFileUrl = Application.class.getResource("log4j.properties"); } } catch (MalformedURLException mue) { // revert to the properties file in the jar logConfigFileUrl = Application.class.getResource("log4j.properties"); } PropertyConfigurator.configure(logConfigFileUrl); } private final RexsterServer httpServer; private final RexsterServer rexproServer; private final RexsterApplication rexsterApplication; private final RexsterProperties properties; private final ReporterConfig reporterConfig; /** * Check for configuration file changes. */ private final FileAlterationMonitor configurationMonitor; public Application(final RexsterProperties properties, final FileAlterationObserver rexsterConfigurationObserver) throws Exception { // watch rexster.xml for changes rexsterConfigurationObserver.addListener(properties); this.configurationMonitor = new FileAlterationMonitor(properties.getConfigCheckInterval()); configurationMonitor.addObserver(rexsterConfigurationObserver); // get the graph configurations from the XML config file this.properties = properties; this.rexsterApplication = new XmlRexsterApplication(this.properties); this.reporterConfig = new ReporterConfig(properties, this.rexsterApplication.getMetricRegistry()); this.httpServer = new HttpRexsterServer(properties); this.rexproServer = new RexProRexsterServer(properties, true); } public void start() throws Exception { configureScriptEngine(); properties.addListener(new RexsterProperties.RexsterPropertiesListener() { @Override public void propertiesChanged(final XMLConfiguration configuration) { configureScriptEngine(); } }); this.httpServer.start(this.rexsterApplication); this.rexproServer.start(this.rexsterApplication); this.configurationMonitor.start(); startShutdownManager(this.properties); } private void configureScriptEngine() { // the EngineController needs to be configured statically before requests start serving so that it can // properly construct ScriptEngine objects with the correct reset policy. allow scriptengines to be // configured so that folks can drop in different gremlin flavors. final List<EngineConfiguration> configuredScriptEngines = new ArrayList<EngineConfiguration>(); final List<HierarchicalConfiguration> configs = this.properties.getScriptEngines(); for(HierarchicalConfiguration config : configs) { configuredScriptEngines.add(new EngineConfiguration(config)); } EngineController.configure(configuredScriptEngines); } public void stop() { try { this.configurationMonitor.stop(); } catch (Exception ex) { logger.debug("Error shutting down the configuration monitor"); } try { this.httpServer.stop(); } catch (Exception ex) { logger.debug("Error shutting down Rexster Server ignored.", ex); } try { this.rexproServer.stop(); } catch (Exception ex) { logger.debug("Error shutting down RexPro Server ignored.", ex); } try { this.rexsterApplication.stop(); } catch (Exception ex) { logger.warn("Error while shutting down graphs. All graphs may not have been shutdown cleanly."); } } private void startShutdownManager(final RexsterProperties properties) throws Exception { final ShutdownManager shutdownManager = new ShutdownManager(properties); //Register a shutdown hook shutdownManager.registerShutdownListener(new ShutdownManager.ShutdownListener() { public void shutdown() { // shutdown grizzly/graphs stop(); } }); //Start the shutdown listener shutdownManager.start(); //Wait for a shutdown request and all shutdown listeners to complete shutdownManager.waitForShutdown(); } public static void main(final String[] args) { logger.info(".:Welcome to Rexster:."); // properties from XML can be overriden by entries issued from the command line final RexsterSettings settings = new RexsterSettings(args); initializeDebugLogging(settings); final RexsterCommandLine line = settings.getCommand(); if (settings.getPrimeCommand().equals(RexsterSettings.COMMAND_START)) { try { new Application(settings.getProperties(), createRexsterConfigurationObserver(settings)).start(); } catch (BindException be) { logger.fatal("Could not start Rexster Server. A port that Rexster needs is in use."); } catch (Exception ex) { logger.fatal("The Rexster Server could not be started", ex); } } else if (settings.getPrimeCommand().equals(RexsterSettings.COMMAND_VERSION)) { logger.info(String.format("Rexster version [%s]", Tokens.REXSTER_VERSION)); } else if (settings.getPrimeCommand().equals(RexsterSettings.COMMAND_STOP)) { if (line.hasCommandParameters() && line.getCommandParameters().hasOption("wait")) { issueControlCommand(line, ShutdownManager.COMMAND_SHUTDOWN_WAIT); } else { issueControlCommand(line, ShutdownManager.COMMAND_SHUTDOWN_NO_WAIT); } } else if (settings.getPrimeCommand().equals(RexsterSettings.COMMAND_STATUS)) { issueControlCommand(line, ShutdownManager.COMMAND_STATUS); } else { settings.printHelp(); } } /** * Initialize debug level logging for rexster if the setting is turned on from the command line. */ private static void initializeDebugLogging(final RexsterSettings settings) { if (settings.isDebug()) { // turn on all logging for jersey -- this is debug mode for (String l : Collections.list(LogManager.getLogManager().getLoggerNames())) { java.util.logging.Logger logger = java.util.logging.Logger.getLogger(l); logger.setLevel(Level.ALL); // remove old handlers for (Handler handler : logger.getHandlers()) { logger.removeHandler(handler); } // route all logging from java.util.Logging to log4net final Handler handler = new JuliToLog4jHandler(); handler.setLevel(Level.ALL); java.util.logging.Logger.getLogger(l).addHandler(handler); } } else { // turn off all logging for jersey for (String l : Collections.list(LogManager.getLogManager().getLoggerNames())) { java.util.logging.Logger.getLogger(l).setLevel(Level.OFF); } } } /** * Construct an observer for watching the rexster configuration file. */ private static FileAlterationObserver createRexsterConfigurationObserver(final RexsterSettings settings) { final File rexsterConfigurationFile = settings.getRexsterXmlFile(); final File absolute = new File(rexsterConfigurationFile.getAbsolutePath()); final File rexsterConfigurationDirectory = absolute.getParentFile(); logger.info(String.format("Rexster is watching [%s] for change.", rexsterConfigurationFile.getAbsolutePath())); // follow the rexster configuration file directory and only the rexster.xml equivalent within that directory. return new FileAlterationObserver(rexsterConfigurationDirectory, FileFilterUtils.and(FileFileFilter.FILE, FileFilterUtils.nameFileFilter(rexsterConfigurationFile.getName()))); } private static int parseInt(final String intString, final int intDefault) { try { return Integer.parseInt(intString); } catch (NumberFormatException nfe) { return intDefault; } } private static void issueControlCommand(final RexsterCommandLine line, final String command) { final String host = line.getCommandOption("rexsterhost", RexsterSettings.DEFAULT_HOST); final String portString = line.getCommandOption("rexsterport", null); final int port = parseInt(portString, RexsterSettings.DEFAULT_SHUTDOWN_PORT); if (line.hasCommandOption("rexsterport") && !Integer.toString(port).equals(portString)) { logger.warn("The value of the <port> parameter was not a valid value. Utilizing the default port of " + port + "."); } Socket shutdownConnection = null; try { final InetAddress hostAddress = InetAddress.getByName(host); shutdownConnection = new Socket(hostAddress, port); shutdownConnection.setSoTimeout(30000); final BufferedReader reader = new BufferedReader(new InputStreamReader(shutdownConnection.getInputStream())); final PrintStream writer = new PrintStream(shutdownConnection.getOutputStream()); try { writer.println(command); writer.flush(); while (true) { final String theLine = reader.readLine(); if (theLine == null) { break; } System.out.println(theLine); } } finally { IOUtils.closeQuietly(reader); IOUtils.closeQuietly(writer); } } catch (SocketTimeoutException ste) { // perhaps the -wait option should take an argument for how long to wait for...for now 30 seconds seems // long enough. logger.warn("Taking longer than 30 seconds to shutdown Rexster. Check shutdown status with --status"); } catch (IOException ioe) { // SocketException or ConnectionException would be more exacting here, but don't think much can be done // with an IOException ... will keep it generic for now. logger.warn("Cannot connect to Rexster Server to issue command. It may not be running."); } finally { try { if (shutdownConnection != null) { shutdownConnection.close(); } } catch (IOException ioe) { } } } }