001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.servicemix.http.jetty;
018
019 import java.io.IOException;
020 import java.io.OutputStream;
021 import java.net.InetAddress;
022 import java.net.URL;
023 import java.net.UnknownHostException;
024 import java.util.HashMap;
025 import java.util.Iterator;
026 import java.util.Map;
027
028 import javax.jbi.JBIException;
029 import javax.management.MBeanServer;
030 import javax.servlet.ServletException;
031 import javax.servlet.http.HttpServletRequest;
032 import javax.servlet.http.HttpServletResponse;
033
034 import org.apache.commons.logging.Log;
035 import org.apache.commons.logging.LogFactory;
036 import org.apache.servicemix.http.ContextManager;
037 import org.apache.servicemix.http.HttpBridgeServlet;
038 import org.apache.servicemix.http.HttpConfiguration;
039 import org.apache.servicemix.http.HttpProcessor;
040 import org.apache.servicemix.http.SslParameters;
041 import org.apache.servicemix.common.security.KeystoreManager;
042 import org.apache.servicemix.common.security.AuthenticationService;
043 import org.mortbay.component.AbstractLifeCycle;
044 import org.mortbay.jetty.AbstractConnector;
045 import org.mortbay.jetty.Connector;
046 import org.mortbay.jetty.Handler;
047 import org.mortbay.jetty.HttpConnection;
048 import org.mortbay.jetty.HttpMethods;
049 import org.mortbay.jetty.MimeTypes;
050 import org.mortbay.jetty.Server;
051 import org.mortbay.jetty.handler.AbstractHandler;
052 import org.mortbay.jetty.handler.ContextHandler;
053 import org.mortbay.jetty.handler.ContextHandlerCollection;
054 import org.mortbay.jetty.handler.HandlerCollection;
055 import org.mortbay.jetty.security.Constraint;
056 import org.mortbay.jetty.security.ConstraintMapping;
057 import org.mortbay.jetty.security.SecurityHandler;
058 import org.mortbay.jetty.security.SslSocketConnector;
059 import org.mortbay.jetty.servlet.ServletHandler;
060 import org.mortbay.jetty.servlet.ServletHolder;
061 import org.mortbay.jetty.servlet.ServletMapping;
062 import org.mortbay.management.MBeanContainer;
063 import org.mortbay.thread.BoundedThreadPool;
064 import org.mortbay.thread.ThreadPool;
065 import org.mortbay.util.ByteArrayISO8859Writer;
066 import org.mortbay.util.LazyList;
067 import org.mortbay.util.StringUtil;
068 import org.springframework.core.io.ClassPathResource;
069
070 public class JettyContextManager implements ContextManager {
071
072 private static final Log LOGGER = LogFactory.getLog(JettyContextManager.class);
073
074 private Map<String, Server> servers;
075 private HttpConfiguration configuration;
076 private BoundedThreadPool threadPool;
077 private Map<String, SslParameters> sslParams;
078 private MBeanServer mBeanServer;
079 private MBeanContainer mbeanContainer;
080
081 /**
082 * @return the mbeanServer
083 */
084 public MBeanServer getMBeanServer() {
085 return mBeanServer;
086 }
087
088 /**
089 * @param mBeanServer
090 * the mbeanServer to set
091 */
092 public void setMBeanServer(MBeanServer mBeanServer) {
093 this.mBeanServer = mBeanServer;
094 }
095
096 public void init() throws Exception {
097 if (configuration == null) {
098 configuration = new HttpConfiguration();
099 }
100 if (mBeanServer != null && !configuration.isManaged() && configuration.isJettyManagement()) {
101 mbeanContainer = new MBeanContainer(mBeanServer);
102 }
103 servers = new HashMap<String, Server>();
104 sslParams = new HashMap<String, SslParameters>();
105 BoundedThreadPool btp = new BoundedThreadPool();
106 btp.setMaxThreads(this.configuration.getJettyThreadPoolSize());
107 threadPool = btp;
108 }
109
110 public void shutDown() throws Exception {
111 stop();
112 }
113
114 public void start() throws Exception {
115 threadPool.start();
116 for (Iterator<Server> it = servers.values().iterator(); it.hasNext();) {
117 Server server = it.next();
118 server.start();
119 }
120 }
121
122 public void stop() throws Exception {
123 for (Iterator<Server> it = servers.values().iterator(); it.hasNext();) {
124 Server server = it.next();
125 server.stop();
126 }
127 for (Iterator<Server> it = servers.values().iterator(); it.hasNext();) {
128 Server server = it.next();
129 server.join();
130 Connector[] connectors = server.getConnectors();
131 for (int i = 0; i < connectors.length; i++) {
132 if (connectors[i] instanceof AbstractConnector) {
133 ((AbstractConnector) connectors[i]).join();
134 }
135 }
136 }
137 threadPool.stop();
138 }
139
140 public synchronized Object createContext(String strUrl, HttpProcessor processor) throws Exception {
141 URL url = new URL(strUrl);
142 Server server = getServer(url);
143 if (server == null) {
144 server = createServer(url, processor.getSsl());
145 } else {
146 // Check ssl params
147 SslParameters ssl = sslParams.get(getKey(url));
148 if (ssl != null && !ssl.equals(processor.getSsl())) {
149 throw new Exception("An https server is already created on port " + url.getPort()
150 + " but SSL parameters do not match");
151 }
152 }
153 String path = url.getPath();
154 if (!path.startsWith("/")) {
155 path = "/" + path;
156 }
157 if (path.endsWith("/")) {
158 path = path.substring(0, path.length() - 1);
159 }
160 String pathSlash = path + "/";
161 // Check that context does not exist yet
162 HandlerCollection handlerCollection = (HandlerCollection) server.getHandler();
163 ContextHandlerCollection contexts = (ContextHandlerCollection) handlerCollection.getHandlers()[0];
164 Handler[] handlers = contexts.getHandlers();
165 if (handlers != null) {
166 for (int i = 0; i < handlers.length; i++) {
167 if (handlers[i] instanceof ContextHandler) {
168 ContextHandler h = (ContextHandler) handlers[i];
169 String handlerPath = h.getContextPath() + "/";
170 if (handlerPath.startsWith(pathSlash) || pathSlash.startsWith(handlerPath)) {
171 throw new Exception("The requested context for path '" + path
172 + "' overlaps with an existing context for path: '" + h.getContextPath() + "'");
173 }
174 }
175 }
176 }
177 // Create context
178 ContextHandler context = new ContextHandler();
179 context.setContextPath(path);
180 ServletHolder holder = new ServletHolder();
181 holder.setName("jbiServlet");
182 holder.setClassName(HttpBridgeServlet.class.getName());
183 ServletHandler handler = new ServletHandler();
184 handler.setServlets(new ServletHolder[] {holder});
185 ServletMapping mapping = new ServletMapping();
186 mapping.setServletName("jbiServlet");
187 mapping.setPathSpec("/*");
188 handler.setServletMappings(new ServletMapping[] {mapping});
189 if (processor.getAuthMethod() != null) {
190 SecurityHandler secHandler = new SecurityHandler();
191 ConstraintMapping constraintMapping = new ConstraintMapping();
192 Constraint constraint = new Constraint();
193 constraint.setAuthenticate(true);
194 constraint.setRoles(new String[] {"*"});
195 constraintMapping.setConstraint(constraint);
196 constraintMapping.setPathSpec("/");
197 secHandler.setConstraintMappings(new ConstraintMapping[] {constraintMapping});
198 secHandler.setHandler(handler);
199 secHandler.setAuthMethod(processor.getAuthMethod());
200 JaasUserRealm realm = new JaasUserRealm();
201 if (configuration.getAuthenticationService() != null) {
202 realm.setAuthenticationService(AuthenticationService.Proxy.create(configuration.getAuthenticationService()));
203 }
204 secHandler.setUserRealm(realm);
205 context.setHandler(secHandler);
206 } else {
207 context.setHandler(handler);
208 }
209 context.setAttribute("processor", processor);
210 // add context
211 contexts.addHandler(context);
212 handler.initialize();
213 context.start();
214 return context;
215 }
216
217 public synchronized void remove(Object context) throws Exception {
218 ((ContextHandler) context).stop();
219 for (Iterator<Server> it = servers.values().iterator(); it.hasNext();) {
220 Server server = it.next();
221 HandlerCollection handlerCollection = (HandlerCollection) server.getHandler();
222 ContextHandlerCollection contexts = (ContextHandlerCollection) handlerCollection.getHandlers()[0];
223 Handler[] handlers = contexts.getHandlers();
224 if (handlers != null && handlers.length > 0) {
225 contexts.setHandlers((Handler[]) LazyList.removeFromArray(handlers, context));
226 }
227 }
228 }
229
230 protected Server getServer(URL url) {
231 return servers.get(getKey(url));
232 }
233
234 protected String getKey(URL url) {
235 String host = url.getHost();
236 try {
237 InetAddress addr = InetAddress.getByName(host);
238 if (addr.isAnyLocalAddress()) {
239 host = InetAddress.getLocalHost().getHostName();
240 }
241 } catch (UnknownHostException e) {
242 //unable to lookup host name, using IP address instead
243 }
244 return url.getProtocol() + "://" + host + ":" + url.getPort();
245 }
246
247 protected Server createServer(URL url, SslParameters ssl) throws Exception {
248 boolean isSsl = false;
249 if (url.getProtocol().equals("https")) {
250 // TODO: put ssl default information on HttpConfiguration
251 if (ssl == null) {
252 throw new IllegalArgumentException("https protocol required but no ssl parameters found");
253 }
254 isSsl = true;
255 } else if (!url.getProtocol().equals("http")) {
256 throw new UnsupportedOperationException("Protocol " + url.getProtocol() + " is not supported");
257 }
258 // Create a new server
259 Connector connector;
260 if (isSsl && ssl.isManaged()) {
261 connector = setupManagerSslConnector(url, ssl);
262 } else if (isSsl) {
263 connector = setupSslConnector(url, ssl);
264 } else {
265 String connectorClassName = configuration.getJettyConnectorClassName();
266 try {
267 connector = (Connector) Class.forName(connectorClassName).newInstance();
268 } catch (Exception e) {
269 LOGGER.warn("Could not create a jetty connector of class '" + connectorClassName + "'. Defaulting to "
270 + HttpConfiguration.DEFAULT_JETTY_CONNECTOR_CLASS_NAME);
271 if (LOGGER.isDebugEnabled()) {
272 LOGGER.debug("Reason: " + e.getMessage(), e);
273 }
274 connector = (Connector) Class.forName(HttpConfiguration.DEFAULT_JETTY_CONNECTOR_CLASS_NAME)
275 .newInstance();
276 }
277 }
278 connector.setHost(url.getHost());
279 connector.setPort(url.getPort());
280 connector.setMaxIdleTime(this.configuration.getConnectorMaxIdleTime());
281 Server server = new Server();
282 server.setThreadPool(new ThreadPoolWrapper());
283 server.setConnectors(new Connector[] {connector});
284 ContextHandlerCollection contexts = new ContextHandlerCollection();
285 HandlerCollection handlers = new HandlerCollection();
286 handlers.setHandlers(new Handler[] {contexts, new DisplayServiceHandler()});
287 server.setHandler(handlers);
288 server.start();
289 servers.put(getKey(url), server);
290 sslParams.put(getKey(url), isSsl ? ssl : null);
291 if (mbeanContainer != null) {
292 server.getContainer().addEventListener(mbeanContainer);
293 }
294 return server;
295 }
296
297 private Connector setupSslConnector(URL url, SslParameters ssl) throws JBIException {
298 Connector connector;
299 String keyStore = ssl.getKeyStore();
300 if (keyStore == null) {
301 keyStore = System.getProperty("javax.net.ssl.keyStore", "");
302 if (keyStore == null) {
303 throw new IllegalArgumentException(
304 "keyStore or system property javax.net.ssl.keyStore must be set");
305 }
306 }
307 if (keyStore.startsWith("classpath:")) {
308 try {
309 String res = keyStore.substring(10);
310 URL resurl = new ClassPathResource(res).getURL();
311 keyStore = resurl.toString();
312 } catch (IOException e) {
313 throw new JBIException("Unable to find keystore " + keyStore, e);
314 }
315 }
316 String keyStorePassword = ssl.getKeyStorePassword();
317 if (keyStorePassword == null) {
318 keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword");
319 if (keyStorePassword == null) {
320 throw new IllegalArgumentException(
321 "keyStorePassword or system property javax.net.ssl.keyStorePassword must be set");
322 }
323 }
324 SslSocketConnector sslConnector = new SslSocketConnector();
325 sslConnector.setSslKeyManagerFactoryAlgorithm(ssl.getKeyManagerFactoryAlgorithm());
326 sslConnector.setSslTrustManagerFactoryAlgorithm(ssl.getTrustManagerFactoryAlgorithm());
327 sslConnector.setProtocol(ssl.getProtocol());
328 sslConnector.setConfidentialPort(url.getPort());
329 sslConnector.setPassword(ssl.getKeyStorePassword());
330 sslConnector.setKeyPassword(ssl.getKeyPassword() != null ? ssl.getKeyPassword() : keyStorePassword);
331 sslConnector.setKeystore(keyStore);
332 sslConnector.setKeystoreType(ssl.getKeyStoreType());
333 sslConnector.setNeedClientAuth(ssl.isNeedClientAuth());
334 sslConnector.setWantClientAuth(ssl.isWantClientAuth());
335 // important to set this values for selfsigned keys
336 // otherwise the standard truststore of the jre is used
337 sslConnector.setTruststore(ssl.getTrustStore());
338 if (ssl.getTrustStorePassword() != null) {
339 // check is necessary because if a null password is set
340 // jetty would ask for a password on the comandline
341 sslConnector.setTrustPassword(ssl.getTrustStorePassword());
342 }
343 sslConnector.setTruststoreType(ssl.getTrustStoreType());
344 connector = sslConnector;
345 return connector;
346 }
347
348 private Connector setupManagerSslConnector(URL url, SslParameters ssl) {
349 Connector connector;
350 String keyStore = ssl.getKeyStore();
351 if (keyStore == null) {
352 throw new IllegalArgumentException("keyStore must be set");
353 }
354 ServiceMixSslSocketConnector sslConnector = new ServiceMixSslSocketConnector();
355 sslConnector.setSslKeyManagerFactoryAlgorithm(ssl.getKeyManagerFactoryAlgorithm());
356 sslConnector.setSslTrustManagerFactoryAlgorithm(ssl.getTrustManagerFactoryAlgorithm());
357 sslConnector.setProtocol(ssl.getProtocol());
358 sslConnector.setConfidentialPort(url.getPort());
359 sslConnector.setKeystore(keyStore);
360 sslConnector.setKeyAlias(ssl.getKeyAlias());
361 sslConnector.setNeedClientAuth(ssl.isNeedClientAuth());
362 sslConnector.setWantClientAuth(ssl.isWantClientAuth());
363 sslConnector.setKeystoreManager(KeystoreManager.Proxy.create(getConfiguration().getKeystoreManager()));
364 // important to set this values for selfsigned keys
365 // otherwise the standard truststore of the jre is used
366 sslConnector.setTruststore(ssl.getTrustStore());
367 if (ssl.getTrustStorePassword() != null) {
368 // check is necessary because if a null password is set
369 // jetty would ask for a password on the comandline
370 sslConnector.setTrustPassword(ssl.getTrustStorePassword());
371 }
372 sslConnector.setTruststoreType(ssl.getTrustStoreType());
373 connector = sslConnector;
374 return connector;
375 }
376
377 public HttpConfiguration getConfiguration() {
378 return configuration;
379 }
380
381 public void setConfiguration(HttpConfiguration configuration) {
382 this.configuration = configuration;
383 }
384
385 public ThreadPool getThreadPool() {
386 return threadPool;
387 }
388
389 protected class DisplayServiceHandler extends AbstractHandler {
390
391 public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch)
392 throws IOException, ServletException {
393 if (response.isCommitted() || HttpConnection.getCurrentConnection().getRequest().isHandled()) {
394 return;
395 }
396
397 String method = request.getMethod();
398
399 if (!method.equals(HttpMethods.GET) || !request.getRequestURI().equals("/")) {
400 response.sendError(404);
401 return;
402 }
403
404 response.setStatus(404);
405 response.setContentType(MimeTypes.TEXT_HTML);
406
407 ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(1500);
408
409 String uri = request.getRequestURI();
410 uri = StringUtil.replace(uri, "<", "<");
411 uri = StringUtil.replace(uri, ">", ">");
412
413 writer.write("<HTML>\n<HEAD>\n<TITLE>Error 404 - Not Found");
414 writer.write("</TITLE>\n<BODY>\n<H2>Error 404 - Not Found.</H2>\n");
415 writer.write("No service matched or handled this request.<BR>");
416 writer.write("Known services are: <ul>");
417
418 for (String serverUri : servers.keySet()) {
419 Server server = JettyContextManager.this.servers.get(serverUri);
420 Handler[] handlers = server.getChildHandlersByClass(ContextHandler.class);
421 for (int i = 0; handlers != null && i < handlers.length; i++) {
422 if (!(handlers[i] instanceof ContextHandler)) {
423 continue;
424 }
425 ContextHandler context = (ContextHandler) handlers[i];
426 StringBuffer sb = new StringBuffer();
427 sb.append(serverUri);
428 if (!context.getContextPath().startsWith("/")) {
429 sb.append("/");
430 }
431 sb.append(context.getContextPath());
432 if (!context.getContextPath().endsWith("/")) {
433 sb.append("/");
434 }
435 if (context.isStarted()) {
436 writer.write("<li><a href=\"");
437 writer.write(sb.toString());
438 writer.write("?wsdl\">");
439 writer.write(sb.toString());
440 writer.write("</a></li>\n");
441 } else {
442 writer.write("<li>");
443 writer.write(sb.toString());
444 writer.write(" [Stopped]</li>\n");
445 }
446 }
447 }
448
449 for (int i = 0; i < 10; i++) {
450 writer.write("\n<!-- Padding for IE -->");
451 }
452
453 writer.write("\n</BODY>\n</HTML>\n");
454 writer.flush();
455 response.setContentLength(writer.size());
456 OutputStream out = response.getOutputStream();
457 writer.writeTo(out);
458 out.close();
459 }
460
461 }
462
463 protected class ThreadPoolWrapper extends AbstractLifeCycle implements ThreadPool {
464
465 public boolean dispatch(Runnable job) {
466 if (LOGGER.isDebugEnabled()) {
467 LOGGER.debug("Dispatching job: " + job);
468 }
469 return threadPool.dispatch(job);
470 }
471
472 public int getIdleThreads() {
473 return threadPool.getIdleThreads();
474 }
475
476 public int getThreads() {
477 return threadPool.getThreads();
478 }
479
480 public void join() throws InterruptedException {
481 }
482
483 public boolean isLowOnThreads() {
484 return threadPool.isLowOnThreads();
485 }
486 }
487
488 public HttpProcessor getMainProcessor() {
489 throw new IllegalStateException("ServerManager is not managed");
490 }
491
492 }