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 */
020package org.sonar.process;
021
022import org.slf4j.LoggerFactory;
023
024public class ProcessEntryPoint implements Stoppable {
025
026  public static final String PROPERTY_PROCESS_KEY = "process.key";
027  public static final String PROPERTY_PROCESS_INDEX = "process.index";
028  public static final String PROPERTY_TERMINATION_TIMEOUT = "process.terminationTimeout";
029  public static final String PROPERTY_SHARED_PATH = "process.sharedDir";
030
031  private final Props props;
032  private final Lifecycle lifecycle = new Lifecycle();
033  private final ProcessCommands commands;
034  private final SystemExit exit;
035  private volatile Monitored monitored;
036  private volatile StopperThread stopperThread;
037  private final StopWatcher stopWatcher;
038
039  // new Runnable() is important to avoid conflict of call to ProcessEntryPoint#stop() with Thread#stop()
040  private Thread shutdownHook = new Thread(new Runnable() {
041    @Override
042    public void run() {
043      exit.setInShutdownHook();
044      stop();
045    }
046  });
047
048  ProcessEntryPoint(Props props, SystemExit exit, ProcessCommands commands) {
049    this.props = props;
050    this.exit = exit;
051    this.commands = commands;
052    this.stopWatcher = new StopWatcher(commands, this);
053  }
054
055  public Props getProps() {
056    return props;
057  }
058
059  public String getKey() {
060    return props.nonNullValue(PROPERTY_PROCESS_KEY);
061  }
062
063  /**
064   * Launch process and waits until it's down
065   */
066  public void launch(Monitored mp) {
067    if (!lifecycle.tryToMoveTo(Lifecycle.State.STARTING)) {
068      throw new IllegalStateException("Already started");
069    }
070    monitored = mp;
071
072    try {
073      LoggerFactory.getLogger(getClass()).info("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), Integer.parseInt(props.nonNullValue(PROPERTY_PROCESS_INDEX)));
139    return new ProcessEntryPoint(props, new SystemExit(), commands);
140  }
141}