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.geronimo.jetty6; 018 019 import java.io.IOException; 020 import java.io.InputStream; 021 import java.io.OutputStream; 022 import java.net.URI; 023 import java.net.URISyntaxException; 024 import java.util.HashMap; 025 import java.util.Map; 026 027 import javax.servlet.ServletException; 028 import javax.servlet.http.HttpServletRequest; 029 import javax.servlet.http.HttpServletResponse; 030 031 import org.apache.geronimo.security.ContextManager; 032 import org.apache.geronimo.webservices.WebServiceContainer; 033 import org.mortbay.jetty.HttpException; 034 import org.mortbay.jetty.Request; 035 import org.mortbay.jetty.Response; 036 import org.mortbay.jetty.handler.ContextHandler; 037 import org.mortbay.jetty.security.Authenticator; 038 import org.mortbay.jetty.security.BasicAuthenticator; 039 import org.mortbay.jetty.security.ClientCertAuthenticator; 040 import org.mortbay.jetty.security.DigestAuthenticator; 041 042 /** 043 * Delegates requests to a WebServiceContainer which is presumably for an EJB WebService. 044 * <p/> 045 * WebServiceContainer delegates to an EJBContainer that will ultimately provide the JNDI, 046 * TX, and Security services for this web service. 047 * <p/> 048 * Nothing stopping us from using this for POJOs or other types of webservices if shared 049 * Context (JNDI, tx, security) wasn't required to be supplied by the web context. 050 * <p/> 051 * From a 10,000 foot view the Jetty architecture has: 052 * Container -> Context -> Holder -> Servlet 053 * <p/> 054 * A Container has multiple Contexts, typically webapps 055 * A Context provides the JNDI, TX, and Security for the webapp and has many Holders 056 * A Holder simply wraps each Servlet 057 * <p/> 058 * The POJO Web Service architecture on Jetty looks like this: 059 * Container -> WebApp Context -> JettyPOJOWebServiceHolder -> POJOWebServiceServlet 060 * <p/> 061 * The EJB Web Service architecure, on the other hand, creates one Context for each EJB: 062 * Container -> JettyEJBWebServiceContext 063 * 064 * @version $Rev: 549825 $ $Date: 2007-06-22 10:17:31 -0400 (Fri, 22 Jun 2007) $ 065 */ 066 public class JettyEJBWebServiceContext extends ContextHandler { 067 068 private final String contextPath; 069 private final WebServiceContainer webServiceContainer; 070 private final Authenticator authenticator; 071 private final JAASJettyRealm realm; 072 private final boolean isConfidentialTransportGuarantee; 073 private final boolean isIntegralTransportGuarantee; 074 private final ClassLoader classLoader; 075 076 077 public JettyEJBWebServiceContext(String contextPath, WebServiceContainer webServiceContainer, InternalJAASJettyRealm internalJAASJettyRealm, String realmName, String transportGuarantee, String authMethod, ClassLoader classLoader) { 078 this.contextPath = contextPath; 079 this.webServiceContainer = webServiceContainer; 080 this.setContextPath(contextPath); 081 082 if (internalJAASJettyRealm != null) { 083 realm = new JAASJettyRealm(realmName, internalJAASJettyRealm); 084 //TODO 085 //not used??? 086 //setUserRealm(realm); 087 // this.realm = realm; 088 if ("NONE".equals(transportGuarantee)) { 089 isConfidentialTransportGuarantee = false; 090 isIntegralTransportGuarantee = false; 091 } else if ("INTEGRAL".equals(transportGuarantee)) { 092 isConfidentialTransportGuarantee = false; 093 isIntegralTransportGuarantee = true; 094 } else if ("CONFIDENTIAL".equals(transportGuarantee)) { 095 isConfidentialTransportGuarantee = true; 096 isIntegralTransportGuarantee = false; 097 } else { 098 throw new IllegalArgumentException("Invalid transport-guarantee: " + transportGuarantee); 099 } 100 if ("BASIC".equals(authMethod)) { 101 authenticator = new BasicAuthenticator(); 102 } else if ("DIGEST".equals(authMethod)) { 103 authenticator = new DigestAuthenticator(); 104 } else if ("CLIENT-CERT".equals(authMethod)) { 105 authenticator = new ClientCertAuthenticator(); 106 } else if ("NONE".equals(authMethod)) { 107 authenticator = null; 108 } else { 109 throw new IllegalArgumentException("Invalid authMethod: " + authMethod); 110 } 111 } else { 112 realm = null; 113 authenticator = null; 114 isConfidentialTransportGuarantee = false; 115 isIntegralTransportGuarantee = false; 116 } 117 this.classLoader = classLoader; 118 } 119 120 public String getName() { 121 //need a better name 122 return contextPath; 123 } 124 125 public void handle(String target, HttpServletRequest req, HttpServletResponse res, int dispatch) 126 throws IOException, ServletException 127 { 128 //TODO 129 //do we need to check that this request should be handled by this handler? 130 if (! target.startsWith(contextPath)) { 131 return; 132 } 133 Request jettyRequest = (Request) req; 134 Response jettyResponse = (Response) res; 135 res.setContentType("text/xml"); 136 RequestAdapter request = new RequestAdapter(jettyRequest); 137 ResponseAdapter response = new ResponseAdapter(jettyResponse); 138 139 request.setAttribute(WebServiceContainer.SERVLET_REQUEST, req); 140 request.setAttribute(WebServiceContainer.SERVLET_RESPONSE, res); 141 // TODO: add support for context 142 request.setAttribute(WebServiceContainer.SERVLET_CONTEXT, null); 143 144 if (req.getParameter("wsdl") != null) { 145 try { 146 webServiceContainer.getWsdl(request, response); 147 jettyRequest.setHandled(true); 148 } catch (IOException e) { 149 throw e; 150 } catch (Exception e) { 151 throw (HttpException) new HttpException(500, "Could not fetch wsdl!").initCause(e); 152 } 153 } else { 154 if (isConfidentialTransportGuarantee) { 155 if (!req.isSecure()) { 156 throw new HttpException(403, null); 157 } 158 } else if (isIntegralTransportGuarantee) { 159 if (!jettyRequest.getConnection().isIntegral(jettyRequest)) { 160 throw new HttpException(403, null); 161 } 162 } 163 Thread currentThread = Thread.currentThread(); 164 ClassLoader oldClassLoader = currentThread.getContextClassLoader(); 165 currentThread.setContextClassLoader(classLoader); 166 //hard to imagine this could be anything but null, but.... 167 // Subject oldSubject = ContextManager.getCurrentCaller(); 168 try { 169 if (authenticator != null) { 170 String pathInContext = org.mortbay.util.URIUtil.canonicalPath(req.getContextPath()); 171 if (authenticator.authenticate(realm, pathInContext, jettyRequest, jettyResponse) == null) { 172 throw new HttpException(403, null); 173 } 174 } else { 175 //EJB will figure out correct defaultSubject shortly 176 //TODO consider replacing the GenericEJBContainer.DefaultSubjectInterceptor with this line 177 //setting the defaultSubject. 178 ContextManager.popCallers(null); 179 } 180 try { 181 webServiceContainer.invoke(request, response); 182 jettyRequest.setHandled(true); 183 } catch (IOException e) { 184 throw e; 185 } catch (Exception e) { 186 throw (HttpException) new HttpException(500, "Could not process message!").initCause(e); 187 } 188 } finally { 189 // ContextManager.setCurrentCaller(oldSubject); 190 currentThread.setContextClassLoader(oldClassLoader); 191 } 192 } 193 194 } 195 196 public String getContextPath() { 197 return contextPath; 198 } 199 200 public String getSecurityRealmName() { 201 if (realm == null) { 202 return null; 203 } else { 204 return realm.getSecurityRealmName(); 205 } 206 } 207 208 public static class RequestAdapter implements WebServiceContainer.Request { 209 private final Request request; 210 private URI uri; 211 212 public RequestAdapter(Request request) { 213 this.request = request; 214 } 215 216 public String getHeader(String name) { 217 return request.getHeader(name); 218 } 219 220 public java.net.URI getURI() { 221 if (uri == null) { 222 try { 223 //String uriString = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getRequestURI(); 224 //return new java.net.URI(uri.getScheme(),uri.getHost(),uri.getPath(),uri.); 225 uri = new java.net.URI(request.getScheme(), null, request.getServerName(), request.getServerPort(), request.getRequestURI(), request.getQueryString(), null); 226 } catch (URISyntaxException e) { 227 throw new IllegalStateException(e.getMessage(), e); 228 } 229 } 230 return uri; 231 } 232 233 public int getContentLength() { 234 return request.getContentLength(); 235 } 236 237 public String getContentType() { 238 return request.getContentType(); 239 } 240 241 public InputStream getInputStream() throws IOException { 242 return request.getInputStream(); 243 } 244 245 public int getMethod() { 246 Integer method = (Integer) methods.get(request.getMethod()); 247 return method == null ? UNSUPPORTED : method.intValue(); 248 } 249 250 public String getParameter(String name) { 251 return request.getParameter(name); 252 } 253 254 public Map getParameters() { 255 return request.getParameterMap(); 256 } 257 258 public Object getAttribute(String name) { 259 return request.getAttribute(name); 260 } 261 262 public void setAttribute(String name, Object value) { 263 request.setAttribute(name, value); 264 } 265 266 public String getRemoteAddr() { 267 return request.getRemoteAddr(); 268 } 269 270 public String getContextPath() { 271 //request.getContextPath() isn't working correctly and returned null. 272 //use getRequestURI() for now before it is fixed. 273 //return request.getContextPath(); 274 return request.getRequestURI(); 275 } 276 277 private static final Map methods = new HashMap(); 278 279 static { 280 methods.put("OPTIONS", new Integer(OPTIONS)); 281 methods.put("GET", new Integer(GET)); 282 methods.put("HEAD", new Integer(HEAD)); 283 methods.put("POST", new Integer(POST)); 284 methods.put("PUT", new Integer(PUT)); 285 methods.put("DELETE", new Integer(DELETE)); 286 methods.put("TRACE", new Integer(TRACE)); 287 methods.put("CONNECT", new Integer(CONNECT)); 288 } 289 290 } 291 292 public static class ResponseAdapter implements WebServiceContainer.Response { 293 private final Response response; 294 295 public ResponseAdapter(Response response) { 296 this.response = response; 297 } 298 299 public void setHeader(String name, String value) { 300 response.setHeader(name, value); 301 } 302 303 public String getHeader(String name) { 304 return response.getHeader(name); 305 } 306 307 public OutputStream getOutputStream() { 308 try { 309 return response.getOutputStream(); 310 } catch (IOException e) { 311 throw new IllegalStateException(e.getMessage(), e); 312 } 313 } 314 315 public void setStatusCode(int code) { 316 response.setStatus(code); 317 } 318 319 public int getStatusCode() { 320 return response.getStatus(); 321 } 322 323 public void setContentType(String type) { 324 response.setContentType(type); 325 } 326 327 public String getContentType() { 328 return response.getContentType(); 329 } 330 331 public void setStatusMessage(String responseString) { 332 response.setStatus(response.getStatus(), responseString); 333 } 334 335 public void flushBuffer() throws java.io.IOException{ 336 response.flushBuffer(); 337 } 338 } 339 340 }