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    }