View Javadoc
1   /* 
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    * http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   *
14   */
15  
16  package org.esigate.server;
17  
18  import java.io.File;
19  import java.io.FileInputStream;
20  import java.io.FileNotFoundException;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.security.ProtectionDomain;
24  import java.util.Properties;
25  
26  import org.apache.commons.io.FileUtils;
27  import org.apache.commons.lang3.StringUtils;
28  import org.eclipse.jetty.server.Connector;
29  import org.eclipse.jetty.server.HttpConfiguration;
30  import org.eclipse.jetty.server.HttpConnectionFactory;
31  import org.eclipse.jetty.server.Server;
32  import org.eclipse.jetty.server.ServerConnector;
33  import org.eclipse.jetty.server.handler.HandlerCollection;
34  import org.eclipse.jetty.server.handler.HandlerList;
35  import org.eclipse.jetty.util.thread.QueuedThreadPool;
36  import org.eclipse.jetty.webapp.WebAppContext;
37  import org.esigate.server.metrics.InstrumentedServerConnector;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  import com.codahale.metrics.MetricRegistry;
42  import com.codahale.metrics.Timer;
43  import com.codahale.metrics.jetty9.InstrumentedConnectionFactory;
44  import com.codahale.metrics.jetty9.InstrumentedHandler;
45  import com.codahale.metrics.jetty9.InstrumentedQueuedThreadPool;
46  
47  /**
48   * The bootstrap code for esigate-server, using jetty.
49   * 
50   * 
51   * <p>
52   * Inspiration from Ole Christian Rynning (http://open.bekk.no/embedded-jetty-7-webapp-executable-with-maven/)
53   * 
54   * @author Nicolas Richeton
55   * 
56   */
57  public final class EsigateServer {
58  
59      private static final Logger LOG = LoggerFactory.getLogger(EsigateServer.class);
60  
61      private static String contextPath;
62      private static int controlPort;
63      private static String extraClasspath;
64      private static long idleTimeout = 0;
65      private static int maxThreads = 0;
66      private static int minThreads = 0;
67      private static int outputBufferSize = 0;
68      private static int port;
69      private static String sessionCookieName;
70      private static final int PROPERTY_DEFAULT_CONTROL_PORT = 8081;
71      private static final int PROPERTY_DEFAULT_HTTP_PORT = 8080;
72      private static final String PROPERTY_PREFIX = "server.";
73      private static Server srv = null;
74  
75      private EsigateServer() {
76  
77      }
78  
79      /**
80       * Get an integer from System properties
81       * 
82       * @param prefix
83       * @param name
84       * @param defaultValue
85       * @return
86       */
87      private static int getProperty(String prefix, String name, int defaultValue) {
88          int result = defaultValue;
89  
90          try {
91              result = Integer.parseInt(System.getProperty(prefix + name));
92          } catch (NumberFormatException e) {
93              LOG.warn("Value for " + prefix + name + " must be an integer. Using default " + defaultValue);
94          }
95          return result;
96      }
97  
98      /**
99       * Get String from System properties
100      * 
101      * @param prefix
102      * @param name
103      * @param defaultValue
104      * @return
105      */
106     private static String getProperty(String prefix, String name, String defaultValue) {
107         return System.getProperty(prefix + name, defaultValue);
108     }
109 
110     /**
111      * Read server configuration from System properties and from server.properties.
112      */
113     public static void init() {
114 
115         // Get configuration
116 
117         // Read from "server.properties" or custom file.
118         String configFile = null;
119         Properties serverProperties = new Properties();
120         try {
121             configFile = System.getProperty(PROPERTY_PREFIX + "config", "server.properties");
122             LOG.info("Loading server configuration from " + configFile);
123 
124             try (InputStream is = new FileInputStream(configFile)) {
125                 serverProperties.load(is);
126             }
127 
128         } catch (FileNotFoundException e) {
129             LOG.warn(configFile + " not found.");
130         } catch (IOException e) {
131             LOG.error("Unexpected error reading " + configFile);
132         }
133 
134         init(serverProperties);
135     }
136 
137     /**
138      * Set the provided server configuration then read configuration from System properties or load defaults.
139      * 
140      * @param configuration
141      *            configuration to use.
142      */
143     public static void init(Properties configuration) {
144 
145         for (Object prop : configuration.keySet()) {
146             String serverPropertyName = (String) prop;
147             System.setProperty(PROPERTY_PREFIX + serverPropertyName, configuration.getProperty(serverPropertyName));
148         }
149 
150         // Read system properties
151         LOG.info("Using configuration provided using '-D' parameter and/or default values");
152         EsigateServer.port = getProperty(PROPERTY_PREFIX, "port", PROPERTY_DEFAULT_HTTP_PORT);
153         EsigateServer.controlPort = getProperty(PROPERTY_PREFIX, "controlPort", PROPERTY_DEFAULT_CONTROL_PORT);
154         EsigateServer.contextPath = getProperty(PROPERTY_PREFIX, "contextPath", "/");
155         EsigateServer.extraClasspath = getProperty(PROPERTY_PREFIX, "extraClasspath", null);
156         EsigateServer.maxThreads = getProperty(PROPERTY_PREFIX, "maxThreads", 500);
157         EsigateServer.minThreads = getProperty(PROPERTY_PREFIX, "minThreads", 40);
158         EsigateServer.outputBufferSize = getProperty(PROPERTY_PREFIX, "outputBufferSize", 8 * 1024);
159         EsigateServer.idleTimeout = getProperty(PROPERTY_PREFIX, "idleTimeout", 30 * 1000);
160         EsigateServer.sessionCookieName = getProperty(PROPERTY_PREFIX, "sessionCookieName", null);
161     }
162 
163     /**
164      * Returns current control port.
165      * 
166      * @return current control port.
167      */
168     public static int getControlPort() {
169         return controlPort;
170     }
171 
172     /**
173      * Esigate Server entry point.
174      * 
175      * @param args
176      *            command line arguments.
177      * @throws Exception
178      *             when server cannot be started.
179      */
180     public static void main(String[] args) throws Exception {
181 
182         if (args.length < 1) {
183             EsigateServer.usage();
184             return;
185         }
186 
187         switch (args[0]) {
188         case "start":
189             EsigateServer.init();
190             EsigateServer.start();
191             break;
192 
193         case "stop":
194             EsigateServer.stop();
195             break;
196 
197         default:
198             EsigateServer.usage();
199             break;
200         }
201     }
202 
203     private static File resetTempDirectory(String currentDir) throws IOException {
204         File workDir;
205         // Currently disabled because this may be dangerous.
206         // if (EsigateServer.workPath != null) {
207         // workDir = new File(EsigateServer.workPath);
208         // } else {
209         workDir = new File(currentDir, "work");
210         // }
211         if (workDir.exists()) {
212             try {
213                 FileUtils.cleanDirectory(workDir);
214             } catch (IllegalArgumentException e) {
215                 // Strange behavior : if this directory exists, it disappears a
216                 // few ms later, causing this exception. We can ignore since we
217                 // initially wanted to delete it.
218                 LOG.info("Info: issue while deleting work directory, it was already deleted. Not a problem.");
219             }
220         }
221 
222         return workDir;
223 
224     }
225 
226     /**
227      * Create and start server.
228      * 
229      * @throws Exception
230      *             when server cannot be started.
231      */
232     public static void start() throws Exception {
233         MetricRegistry registry = new MetricRegistry();
234 
235         QueuedThreadPool threadPool = new InstrumentedQueuedThreadPool(registry);
236         threadPool.setName("esigate");
237         threadPool.setMaxThreads(maxThreads);
238         threadPool.setMinThreads(minThreads);
239 
240         srv = new Server(threadPool);
241         srv.setStopAtShutdown(true);
242         srv.setStopTimeout(5000);
243 
244         // HTTP Configuration
245         HttpConfiguration httpConfig = new HttpConfiguration();
246         httpConfig.setOutputBufferSize(outputBufferSize);
247         httpConfig.setSendServerVersion(false);
248         Timer processTime = registry.timer("processTime");
249 
250         try (ServerConnector connector =
251                 new InstrumentedServerConnector("main", EsigateServer.port, srv, registry,
252                         new InstrumentedConnectionFactory(new HttpConnectionFactory(httpConfig), processTime));
253                 ServerConnector controlConnector = new ServerConnector(srv)) {
254 
255             // Main connector
256             connector.setIdleTimeout(EsigateServer.idleTimeout);
257             connector.setSoLingerTime(-1);
258             connector.setName("main");
259             connector.setAcceptQueueSize(200);
260 
261             // Control connector
262             controlConnector.setHost("127.0.0.1");
263             controlConnector.setPort(EsigateServer.controlPort);
264             controlConnector.setName("control");
265 
266             srv.setConnectors(new Connector[] {connector, controlConnector});
267             // War
268             ProtectionDomain protectionDomain = EsigateServer.class.getProtectionDomain();
269             String warFile = protectionDomain.getCodeSource().getLocation().toExternalForm();
270             String currentDir = new File(protectionDomain.getCodeSource().getLocation().getPath()).getParent();
271 
272             File workDir = resetTempDirectory(currentDir);
273 
274             WebAppContext context = new WebAppContext(warFile, EsigateServer.contextPath);
275             context.setServer(srv);
276             context.setTempDirectory(workDir);
277             if (StringUtils.isNoneEmpty(sessionCookieName)) {
278                 context.getSessionHandler().setSessionCookie(sessionCookieName);
279             }
280             // Add extra classpath (allows to add extensions).
281             if (EsigateServer.extraClasspath != null) {
282                 context.setExtraClasspath(EsigateServer.extraClasspath);
283             }
284 
285             // Add the handlers
286             HandlerCollection handlers = new HandlerList();
287             // control handler must be the first one.
288             // Work in progress, currently disabled.
289             handlers.addHandler(new ControlHandler(registry));
290             InstrumentedHandler ih = new InstrumentedHandler(registry);
291             ih.setName("main");
292             ih.setHandler(context);
293             handlers.addHandler(ih);
294 
295             srv.setHandler(handlers);
296             srv.start();
297             srv.join();
298 
299         }
300 
301     }
302 
303     /**
304      * Check if server is started.
305      * 
306      * @return true if started.
307      */
308     public static boolean isStarted() {
309         return srv != null && srv.isStarted();
310     }
311 
312     /**
313      * Send a shutdown request to esigate server.
314      */
315     public static void stop() {
316         ControlHandler.shutdown(EsigateServer.controlPort);
317     }
318 
319     /**
320      * Display usage information.
321      */
322     private static void usage() {
323         StringBuilder usageText = new StringBuilder();
324         usageText.append("Usage: java -D").append(PROPERTY_PREFIX)
325                 .append("config=esigate.properties -jar esigate-server.jar [start|stop]\n\t");
326         usageText.append("start    Start the server (default)\n\t");
327         usageText.append("stop     Stop the server gracefully\n\t");
328 
329         System.out.println(usageText.toString());
330         System.exit(-1);
331     }
332 }