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 }