001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */package org.apache.cxf.transport.http_jetty;
019:
020: import java.io.IOException;
021: import java.net.URL;
022: import java.security.GeneralSecurityException;
023: import java.util.List;
024: import java.util.logging.Level;
025: import java.util.logging.Logger;
026:
027: import javax.annotation.PostConstruct;
028: import javax.annotation.Resource;
029:
030: import org.apache.cxf.Bus;
031: import org.apache.cxf.common.logging.LogUtils;
032: import org.apache.cxf.configuration.jsse.TLSServerParameters;
033: import org.apache.cxf.transport.HttpUriMapper;
034: import org.apache.cxf.transport.https_jetty.JettySslConnectorFactory;
035: import org.mortbay.jetty.AbstractConnector;
036: import org.mortbay.jetty.Connector;
037: import org.mortbay.jetty.Handler;
038: import org.mortbay.jetty.Server;
039: import org.mortbay.jetty.handler.ContextHandler;
040: import org.mortbay.jetty.handler.ContextHandlerCollection;
041: import org.mortbay.jetty.handler.HandlerList;
042: import org.mortbay.jetty.nio.SelectChannelConnector;
043: import org.mortbay.jetty.security.SslSocketConnector;
044: import org.mortbay.jetty.servlet.HashSessionIdManager;
045: import org.mortbay.jetty.servlet.HashSessionManager;
046: import org.mortbay.jetty.servlet.SessionHandler;
047: import org.mortbay.thread.BoundedThreadPool;
048:
049: /**
050: * This class is the Jetty HTTP Server Engine that is configured to
051: * work off of a designated port. The port will be enabled for
052: * "http" or "https" depending upon its successful configuration.
053: */
054: public class JettyHTTPServerEngine implements ServerEngine {
055: private static final long serialVersionUID = 1L;
056:
057: private static final Logger LOG = LogUtils
058: .getL7dLogger(JettyHTTPServerEngine.class);
059:
060: /**
061: * The bus.
062: */
063: private Bus bus;
064:
065: /**
066: * This is the Jetty HTTP Server Engine Factory. This factory caches some
067: * engines based on port numbers.
068: */
069: private JettyHTTPServerEngineFactory factory;
070:
071: /**
072: * This is the network port for which this engine is allocated.
073: */
074: private int port;
075:
076: /**
077: * This field holds the protocol for which this engine is
078: * enabled, i.e. "http" or "https".
079: */
080: private String protocol = "http";
081:
082: private Boolean isSessionSupport = false;
083: private int servantCount;
084: private Server server;
085: private Connector connector;
086: private List<Handler> handlers;
087: private JettyConnectorFactory connectorFactory;
088: private ContextHandlerCollection contexts;
089:
090: /**
091: * This field holds the TLS ServerParameters that are programatically
092: * configured. The tlsServerParamers (due to JAXB) holds the struct
093: * placed by SpringConfig.
094: */
095: private TLSServerParameters tlsServerParameters;
096:
097: /**
098: * This field hold the threading parameters for this particular engine.
099: */
100: private ThreadingParameters threadingParameters;
101:
102: /**
103: * This boolean signfies that SpringConfig is over. finalizeConfig
104: * has been called.
105: */
106: private boolean configFinalized;
107:
108: /**
109: * This constructor is called by the JettyHTTPServerEngineFactory.
110: */
111: JettyHTTPServerEngine(JettyHTTPServerEngineFactory fac, Bus bus,
112: int port) {
113: this .bus = bus;
114: this .factory = fac;
115: this .port = port;
116: }
117:
118: public JettyHTTPServerEngine() {
119:
120: }
121:
122: public void setJettyHTTPServerEngineFactory(
123: JettyHTTPServerEngineFactory fac) {
124: factory = fac;
125: }
126:
127: public void setPort(int p) {
128: port = p;
129: }
130:
131: /**
132: * The bus.
133: */
134: @Resource(name="bus")
135: public void setBus(Bus b) {
136: bus = b;
137: }
138:
139: /**
140: * Returns the protocol "http" or "https" for which this engine
141: * was configured.
142: */
143: public String getProtocol() {
144: return protocol;
145: }
146:
147: /**
148: * Returns the port number for which this server engine was configured.
149: * @return
150: */
151: public int getPort() {
152: return port;
153: }
154:
155: /**
156: * This method will shut down the server engine and
157: * remove it from the factory's cache.
158: */
159: public void shutdown() {
160: factory.destroyForPort(port);
161: }
162:
163: /**
164: * get the jetty server instance
165: * @return
166: */
167: public Server getServer() {
168: return server;
169: }
170:
171: /**
172: * Set the jetty server instance
173: * @param s
174: */
175: public void setServer(Server s) {
176: server = s;
177: }
178:
179: /**
180: * set the jetty server's connector
181: * @param c
182: */
183: public void setConnector(Connector c) {
184: connector = c;
185: }
186:
187: /**
188: * set the jetty server's handlers
189: * @param h
190: */
191:
192: public void setHandlers(List<Handler> h) {
193: handlers = h;
194: }
195:
196: public void setSessionSupport(boolean support) {
197: isSessionSupport = support;
198: }
199:
200: public boolean isSessionSupport() {
201: return isSessionSupport;
202: }
203:
204: public List<Handler> getHandlers() {
205: return handlers;
206: }
207:
208: public Connector getConnector() {
209: return connector;
210: }
211:
212: /**
213: * Register a servant.
214: *
215: * @param url the URL associated with the servant
216: * @param handler notified on incoming HTTP requests
217: */
218: public synchronized void addServant(URL url,
219: JettyHTTPHandler handler) {
220: if (server == null) {
221: // create a new jetty server instance if there is no server there
222: server = new Server();
223: if (connector == null) {
224: connector = connectorFactory.createConnector(port);
225: }
226: server.addConnector(connector);
227: if (handlers != null && handlers.size() > 0) {
228: HandlerList handlerList = new HandlerList();
229: for (Handler h : handlers) {
230: handlerList.addHandler(h);
231: }
232: server.addHandler(handlerList);
233: }
234: contexts = new ContextHandlerCollection();
235: server.addHandler(contexts);
236: try {
237: server.start();
238: AbstractConnector aconn = (AbstractConnector) connector;
239: if (aconn.getThreadPool() instanceof BoundedThreadPool
240: && isSetThreadingParameters()) {
241: BoundedThreadPool pool = (BoundedThreadPool) aconn
242: .getThreadPool();
243: if (getThreadingParameters().isSetMinThreads()) {
244: pool.setMinThreads(getThreadingParameters()
245: .getMinThreads());
246: }
247: if (getThreadingParameters().isSetMaxThreads()) {
248: pool.setMaxThreads(getThreadingParameters()
249: .getMaxThreads());
250: }
251: }
252: } catch (Exception e) {
253: LOG.log(Level.SEVERE, "START_UP_SERVER_FAILED_MSG",
254: new Object[] { e.getMessage() });
255: //problem starting server
256: try {
257: server.stop();
258: server.destroy();
259: } catch (Exception ex) {
260: LOG.log(Level.SEVERE, "START_UP_SERVER_FAILED_MSG",
261: new Object[] { e.getMessage() });
262: }
263: }
264: }
265:
266: String contextName = HttpUriMapper
267: .getContextName(url.getPath());
268: ContextHandler context = new ContextHandler();
269: context.setContextPath(contextName);
270:
271: // bind the jetty http handler with the context handler
272: context.setHandler(handler);
273: if (isSessionSupport) {
274: HashSessionManager sessionManager = new HashSessionManager();
275: SessionHandler sessionHandler = new SessionHandler(
276: sessionManager);
277: HashSessionIdManager idManager = new HashSessionIdManager();
278: sessionManager.setIdManager(idManager);
279: context.addHandler(sessionHandler);
280: }
281: contexts.addHandler(context);
282:
283: final String smap = HttpUriMapper
284: .getResourceBase(url.getPath());
285: handler.setName(smap);
286:
287: if (contexts.isStarted()) {
288: try {
289: context.start();
290: } catch (Exception ex) {
291: LOG.log(Level.WARNING, "ADD_HANDLER_FAILED_MSG",
292: new Object[] { ex.getMessage() });
293: }
294: }
295:
296: ++servantCount;
297: }
298:
299: /**
300: * Remove a previously registered servant.
301: *
302: * @param url the URL the servant was registered against.
303: */
304: public synchronized void removeServant(URL url) {
305:
306: String contextName = HttpUriMapper
307: .getContextName(url.getPath());
308:
309: boolean found = false;
310: // REVISIT:After a stop(), the server is null, and therefore this
311: // operation shouldn't find a handler
312: if (server != null) {
313: for (Handler handler : contexts
314: .getChildHandlersByClass(ContextHandler.class)) {
315: ContextHandler contextHandler = null;
316: if (handler instanceof ContextHandler) {
317: contextHandler = (ContextHandler) handler;
318: if (contextName.equals(contextHandler
319: .getContextPath())) {
320: try {
321: contexts.removeHandler(handler);
322: handler.stop();
323: handler.destroy();
324: } catch (Exception ex) {
325: LOG.log(Level.WARNING,
326: "REMOVE_HANDLER_FAILED_MSG",
327: new Object[] { ex.getMessage() });
328: }
329:
330: }
331: found = true;
332: }
333: }
334: }
335: if (!found) {
336: LOG.log(Level.WARNING, "CAN_NOT_FIND_HANDLER_MSG",
337: new Object[] { url });
338: }
339:
340: --servantCount;
341: /* Bug in Jetty, we cannot do this. If we restart later, data goes off
342: * someplace unknown
343: if (servantCount == 0) {
344: server.removeListener(listener);
345: }
346: */
347: }
348:
349: /**
350: * Get a registered servant.
351: *
352: * @param url the associated URL
353: * @return the HttpHandler if registered
354: */
355: public synchronized Handler getServant(URL url) {
356: String contextName = HttpUriMapper
357: .getContextName(url.getPath());
358: //final String smap = HttpUriMapper.getResourceBase(url.getPath());
359:
360: Handler ret = null;
361: // After a stop(), the server is null, and therefore this
362: // operation should return null.
363: if (server != null) {
364: for (Handler handler : server
365: .getChildHandlersByClass(ContextHandler.class)) {
366: ContextHandler contextHandler = null;
367: if (handler instanceof ContextHandler) {
368: contextHandler = (ContextHandler) handler;
369: if (contextName.equals(contextHandler
370: .getContextPath())) {
371: ret = contextHandler.getHandler();
372: break;
373: }
374: }
375: }
376: }
377: return ret;
378: }
379:
380: /**
381: * Get a registered context handler.
382: *
383: * @param url the associated URL
384: * @return the HttpHandler if registered
385: */
386: public synchronized ContextHandler getContextHandler(URL url) {
387: String contextName = HttpUriMapper
388: .getContextName(url.getPath());
389: ContextHandler ret = null;
390: // After a stop(), the server is null, and therefore this
391: // operation should return null.
392: if (server != null) {
393: for (Handler handler : server
394: .getChildHandlersByClass(ContextHandler.class)) {
395: ContextHandler contextHandler = null;
396: if (handler instanceof ContextHandler) {
397: contextHandler = (ContextHandler) handler;
398: if (contextName.equals(contextHandler
399: .getContextPath())) {
400: ret = contextHandler;
401: break;
402: }
403: }
404: }
405: }
406: return ret;
407: }
408:
409: protected void retrieveListenerFactory() {
410: if (tlsServerParameters != null) {
411: if (null != connector
412: && !(connector instanceof SslSocketConnector)) {
413: throw new RuntimeException("Connector " + connector
414: + " for JettyServerEngine Port " + port
415: + " does not support SSL connections.");
416: }
417: connectorFactory = getHTTPSConnectorFactory(tlsServerParameters);
418: protocol = "https";
419:
420: } else {
421: if (connector instanceof SslSocketConnector) {
422: throw new RuntimeException("Connector " + connector
423: + " for JettyServerEngine Port " + port
424: + " does not support non-SSL connections.");
425: }
426: connectorFactory = getHTTPConnectorFactory();
427: protocol = "http";
428: }
429: LOG.fine("Configured port " + port + " for \"" + protocol
430: + "\".");
431: }
432:
433: /**
434: * This method creates a connector factory. If there are TLS parameters
435: * then it creates a TLS enabled one.
436: */
437: protected JettyConnectorFactory getHTTPConnectorFactory() {
438: return new JettyConnectorFactory() {
439: public AbstractConnector createConnector(int porto) {
440: // now we just use the SelectChannelConnector as the default connector
441: SelectChannelConnector result = new SelectChannelConnector();
442:
443: // Regardless the port has to equal the one
444: // we are configured for.
445: assert porto == port;
446:
447: result.setPort(porto);
448: return result;
449: }
450: };
451: }
452:
453: /**
454: * This method creates a connector factory enabled with the JSSE
455: */
456: protected JettyConnectorFactory getHTTPSConnectorFactory(
457: TLSServerParameters tlsParams) {
458: return new JettySslConnectorFactory(tlsParams);
459: }
460:
461: /**
462: * This method is called after configure on this object.
463: */
464: @PostConstruct
465: protected void finalizeConfig() throws GeneralSecurityException,
466: IOException {
467: retrieveEngineFactory();
468: retrieveListenerFactory();
469: checkConnectorPort();
470: this .configFinalized = true;
471: }
472:
473: private void retrieveEngineFactory() {
474: if (null != bus && null == factory) {
475: factory = bus
476: .getExtension(JettyHTTPServerEngineFactory.class);
477: }
478: }
479:
480: private void checkConnectorPort() throws IOException {
481: if (null != connector && port != connector.getPort()) {
482: throw new IOException("Error: Connector port "
483: + connector.getPort() + " does not match"
484: + " with the server engine port " + port);
485: }
486: }
487:
488: /**
489: * This method is called by the ServerEngine Factory to destroy the
490: * listener.
491: *
492: */
493: protected void stop() throws Exception {
494: if (server != null) {
495: connector.close();
496: server.stop();
497: server.destroy();
498: server = null;
499: }
500: }
501:
502: /**
503: * This method is used to programmatically set the TLSServerParameters.
504: * This method may only be called by the factory.
505: * @throws IOException
506: */
507: public void setTlsServerParameters(TLSServerParameters params)
508: throws IOException {
509:
510: tlsServerParameters = params;
511: if (this .configFinalized) {
512: this .retrieveListenerFactory();
513: }
514: }
515:
516: /**
517: * This method returns the programmatically set TLSServerParameters, not
518: * the TLSServerParametersType, which is the JAXB generated type used
519: * in SpringConfiguration.
520: * @return
521: */
522: public TLSServerParameters getTlsServerParameters() {
523: return tlsServerParameters;
524: }
525:
526: /**
527: * This method sets the threading parameters for this particular
528: * server engine.
529: * This method may only be called by the factory.
530: */
531: public void setThreadingParameters(ThreadingParameters params) {
532: threadingParameters = params;
533: }
534:
535: /**
536: * This method returns whether the threading parameters are set.
537: */
538: public boolean isSetThreadingParameters() {
539: return threadingParameters != null;
540: }
541:
542: /**
543: * This method returns the threading parameters that have been set.
544: * This method may return null, if the threading parameters have not
545: * been set.
546: */
547: public ThreadingParameters getThreadingParameters() {
548: return threadingParameters;
549: }
550:
551: }
|