001 /*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * SonarQube is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
019 */
020 package org.sonar.process;
021
022 import org.slf4j.LoggerFactory;
023
024 public class ProcessEntryPoint implements Stoppable {
025
026 public static final String PROPERTY_PROCESS_KEY = "process.key";
027 public static final String PROPERTY_TERMINATION_TIMEOUT = "process.terminationTimeout";
028 public static final String PROPERTY_SHARED_PATH = "process.sharedDir";
029
030 private final Props props;
031 private final Lifecycle lifecycle = new Lifecycle();
032 private final ProcessCommands commands;
033 private final SystemExit exit;
034 private volatile Monitored monitored;
035 private volatile StopperThread stopperThread;
036 private final StopWatcher stopWatcher;
037
038 // new Runnable() is important to avoid conflict of call to ProcessEntryPoint#stop() with Thread#stop()
039 private Thread shutdownHook = new Thread(new Runnable() {
040 @Override
041 public void run() {
042 exit.setInShutdownHook();
043 stop();
044 }
045 });
046
047 ProcessEntryPoint(Props props, SystemExit exit, ProcessCommands commands) {
048 this.props = props;
049 this.exit = exit;
050 this.commands = commands;
051 this.stopWatcher = new StopWatcher(commands, this);
052 }
053
054 public Props getProps() {
055 return props;
056 }
057
058 public String getKey() {
059 return props.nonNullValue(PROPERTY_PROCESS_KEY);
060 }
061
062 /**
063 * Launch process and waits until it's down
064 */
065 public void launch(Monitored mp) {
066 if (!lifecycle.tryToMoveTo(Lifecycle.State.STARTING)) {
067 throw new IllegalStateException("Already started");
068 }
069 commands.prepare();
070 monitored = mp;
071
072 try {
073 LoggerFactory.getLogger(getClass()).warn("Starting " + getKey());
074 Runtime.getRuntime().addShutdownHook(shutdownHook);
075 stopWatcher.start();
076
077 monitored.start();
078 boolean ready = false;
079 while (!ready) {
080 ready = monitored.isReady();
081 Thread.sleep(200L);
082 }
083
084 // notify monitor that process is ready
085 commands.setReady();
086
087 if (lifecycle.tryToMoveTo(Lifecycle.State.STARTED)) {
088 monitored.awaitStop();
089 }
090 } catch (Exception e) {
091 LoggerFactory.getLogger(getClass()).warn("Fail to start " + getKey(), e);
092
093 } finally {
094 stop();
095 }
096 }
097
098 boolean isStarted() {
099 return lifecycle.getState() == Lifecycle.State.STARTED;
100 }
101
102 /**
103 * Blocks until stopped in a timely fashion (see {@link org.sonar.process.StopperThread})
104 */
105 void stop() {
106 stopAsync();
107 try {
108 // stopperThread is not null for sure
109 // join() does nothing if thread already finished
110 stopperThread.join();
111 lifecycle.tryToMoveTo(Lifecycle.State.STOPPED);
112 } catch (InterruptedException e) {
113 // nothing to do, the process is going to be exited
114 }
115 exit.exit(0);
116 }
117
118 @Override
119 public void stopAsync() {
120 if (lifecycle.tryToMoveTo(Lifecycle.State.STOPPING)) {
121 stopperThread = new StopperThread(monitored, commands, Long.parseLong(props.nonNullValue(PROPERTY_TERMINATION_TIMEOUT)));
122 stopperThread.start();
123 stopWatcher.stopWatching();
124 }
125 }
126
127 Lifecycle.State getState() {
128 return lifecycle.getState();
129 }
130
131 Thread getShutdownHook() {
132 return shutdownHook;
133 }
134
135 public static ProcessEntryPoint createForArguments(String[] args) {
136 Props props = ConfigurationUtils.loadPropsFromCommandLineArgs(args);
137 ProcessCommands commands = new ProcessCommands(
138 props.nonNullValueAsFile(PROPERTY_SHARED_PATH), props.nonNullValue(PROPERTY_PROCESS_KEY));
139 return new ProcessEntryPoint(props, new SystemExit(), commands);
140 }
141 }