001    /**
002     *
003     * Copyright 2003-2004 The Apache Software Foundation
004     *
005     *  Licensed under the Apache License, Version 2.0 (the "License");
006     *  you may not use this file except in compliance with the License.
007     *  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.jetty;
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 org.apache.geronimo.security.ContextManager;
028    import org.apache.geronimo.webservices.WebServiceContainer;
029    import org.mortbay.http.Authenticator;
030    import org.mortbay.http.BasicAuthenticator;
031    import org.mortbay.http.ClientCertAuthenticator;
032    import org.mortbay.http.DigestAuthenticator;
033    import org.mortbay.http.HttpContext;
034    import org.mortbay.http.HttpException;
035    import org.mortbay.http.HttpHandler;
036    import org.mortbay.http.HttpRequest;
037    import org.mortbay.http.HttpResponse;
038    
039    /**
040     * Delegates requests to a WebServiceContainer which is presumably for an EJB WebService.
041     * <p/>
042     * WebServiceContainer delegates to an EJBContainer that will ultimately provide the JNDI,
043     * TX, and Security services for this web service.
044     * <p/>
045     * Nothing stopping us from using this for POJOs or other types of webservices if shared
046     * Context (JNDI, tx, security) wasn't required to be supplied by the web context.
047     * <p/>
048     * From a 10,000 foot view the Jetty architecture has:
049     * Container -> Context -> Holder -> Servlet
050     * <p/>
051     * A Container has multiple Contexts, typically webapps
052     * A Context provides the JNDI, TX, and Security for the webapp and has many Holders
053     * A Holder simply wraps each Servlet
054     * <p/>
055     * The POJO Web Service architecture on Jetty looks like this:
056     * Container -> WebApp Context -> JettyPOJOWebServiceHolder -> POJOWebServiceServlet
057     * <p/>
058     * The EJB Web Service architecure, on the other hand, creates one Context for each EJB:
059     * Container -> JettyEJBWebServiceContext
060     *
061     * @version $Rev: 431706 $ $Date: 2006-08-15 14:19:27 -0700 (Tue, 15 Aug 2006) $
062     */
063    public class JettyEJBWebServiceContext extends HttpContext implements HttpHandler {
064    
065        private final String contextPath;
066        private final WebServiceContainer webServiceContainer;
067        private final Authenticator authenticator;
068        private final JAASJettyRealm realm;
069        private final boolean isConfidentialTransportGuarantee;
070        private final boolean isIntegralTransportGuarantee;
071        private final ClassLoader classLoader;
072    
073        private HttpContext httpContext;
074    
075        public JettyEJBWebServiceContext(String contextPath, WebServiceContainer webServiceContainer, InternalJAASJettyRealm internalJAASJettyRealm, String realmName, String transportGuarantee, String authMethod, ClassLoader classLoader) {
076            this.contextPath = contextPath;
077            this.webServiceContainer = webServiceContainer;
078            if (internalJAASJettyRealm != null) {
079                JAASJettyRealm realm = new JAASJettyRealm(realmName, internalJAASJettyRealm);
080                setRealm(realm);
081                this.realm = realm;
082                if ("NONE".equals(transportGuarantee)) {
083                    isConfidentialTransportGuarantee = false;
084                    isIntegralTransportGuarantee = false;
085                } else if ("INTEGRAL".equals(transportGuarantee)) {
086                    isConfidentialTransportGuarantee = false;
087                    isIntegralTransportGuarantee = true;
088                } else if ("CONFIDENTIAL".equals(transportGuarantee)) {
089                    isConfidentialTransportGuarantee = true;
090                    isIntegralTransportGuarantee = false;
091                } else {
092                    throw new IllegalArgumentException("Invalid transport-guarantee: " + transportGuarantee);
093                }
094                if ("BASIC".equals(authMethod)) {
095                    authenticator = new BasicAuthenticator();
096                } else if ("DIGEST".equals(authMethod)) {
097                    authenticator = new DigestAuthenticator();
098                } else if ("CLIENT-CERT".equals(authMethod)) {
099                    authenticator = new ClientCertAuthenticator();
100                } else if ("NONE".equals(authMethod)) {
101                    authenticator = null;
102                } else {
103                    throw new IllegalArgumentException("Invalid authMethod: " + authMethod);
104                }
105            } else {
106                realm = null;
107                authenticator = null;
108                isConfidentialTransportGuarantee = false;
109                isIntegralTransportGuarantee = false;
110            }
111            this.classLoader = classLoader;
112        }
113    
114        public String getName() {
115            //need a better name
116            return contextPath;
117        }
118    
119        public HttpContext getHttpContext() {
120            return httpContext;
121        }
122    
123        public void initialize(HttpContext httpContext) {
124            this.httpContext = httpContext;
125        }
126    
127        public void handle(HttpRequest req, HttpResponse res) throws HttpException, IOException {
128            req.setContentType("text/xml");
129            RequestAdapter request = new RequestAdapter(req);
130            ResponseAdapter response = new ResponseAdapter(res);
131    
132            if (req.getParameter("wsdl") != null) {
133                try {
134                    webServiceContainer.getWsdl(request, response);
135                    //WHO IS RESPONSIBLE FOR CLOSING OUT?
136                } catch (IOException e) {
137                    throw e;
138                } catch (Exception e) {
139                    throw (HttpException) new HttpException(500, "Could not fetch wsdl!").initCause(e);
140                }
141            } else {
142                if (isConfidentialTransportGuarantee) {
143                    if (!req.isConfidential()) {
144                        throw new HttpException(403);
145                    }
146                } else if (isIntegralTransportGuarantee) {
147                    if (!req.isIntegral()) {
148                        throw new HttpException(403);
149                    }
150                }
151                Thread currentThread = Thread.currentThread();
152                ClassLoader oldClassLoader = currentThread.getContextClassLoader();
153                currentThread.setContextClassLoader(classLoader);
154                //hard to imagine this could be anything but null, but....
155    //            Subject oldSubject = ContextManager.getCurrentCaller();
156                try {
157                    if (authenticator != null) {
158                        String pathInContext = org.mortbay.util.URI.canonicalPath(req.getPath());
159                        if (authenticator.authenticate(realm, pathInContext, req, res) == null) {
160                            throw new HttpException(403);
161                        }
162                    } else {
163                        //EJB will figure out correct defaultSubject shortly
164                        //TODO consider replacing the GenericEJBContainer.DefaultSubjectInterceptor with this line
165                        //setting the defaultSubject.
166                        ContextManager.popCallers(null);
167                    }
168                    try {
169                        webServiceContainer.invoke(request, response);
170                        req.setHandled(true);
171                    } catch (IOException e) {
172                        throw e;
173                    } catch (Exception e) {
174                        throw (HttpException) new HttpException(500, "Could not process message!").initCause(e);
175                    }
176                } finally {
177    //                ContextManager.setCurrentCaller(oldSubject);
178                    currentThread.setContextClassLoader(oldClassLoader);
179                }
180            }
181    
182        }
183    
184        public String getContextPath() {
185            return contextPath;
186        }
187    
188        public String getSecurityRealmName() {
189            if (realm == null) {
190                return null;
191            } else {
192                return realm.getSecurityRealmName();
193            }
194        }
195    
196        public static class RequestAdapter implements WebServiceContainer.Request {
197            private final HttpRequest request;
198            private URI uri;
199    
200            public RequestAdapter(HttpRequest request) {
201                this.request = request;
202            }
203    
204            public String getHeader(String name) {
205                return request.getField(name);
206            }
207    
208            public java.net.URI getURI() {
209                if (uri == null) {
210                    try {
211                        String uriString = request.getScheme() + "://" + request.getHost() + ":" + request.getPort() + request.getURI();
212                        //return new java.net.URI(uri.getScheme(),uri.getHost(),uri.getPath(),uri.);
213                        uri = new java.net.URI(uriString);
214                    } catch (URISyntaxException e) {
215                        throw new IllegalStateException(e.getMessage());
216                    }
217                }
218                return uri;
219            }
220    
221            public int getContentLength() {
222                return request.getContentLength();
223            }
224    
225            public String getContentType() {
226                return request.getContentType();
227            }
228    
229            public InputStream getInputStream() throws IOException {
230                return request.getInputStream();
231            }
232    
233            public int getMethod() {
234                Integer method = (Integer) methods.get(request.getMethod());
235                return method == null ? UNSUPPORTED : method.intValue();
236            }
237    
238            public String getParameter(String name) {
239                return request.getParameter(name);
240            }
241    
242            public Map getParameters() {
243                return request.getParameters();
244            }
245    
246            public Object getAttribute(String name) {
247                return request.getAttribute(name);
248            }
249    
250            public void setAttribute(String name, Object value) {
251                request.setAttribute(name, value);
252            }
253    
254    
255            private static final Map methods = new HashMap();
256    
257            static {
258                methods.put("OPTIONS", new Integer(OPTIONS));
259                methods.put("GET", new Integer(GET));
260                methods.put("HEAD", new Integer(HEAD));
261                methods.put("POST", new Integer(POST));
262                methods.put("PUT", new Integer(PUT));
263                methods.put("DELETE", new Integer(DELETE));
264                methods.put("TRACE", new Integer(TRACE));
265                methods.put("CONNECT", new Integer(CONNECT));
266            }
267    
268        }
269    
270        public static class ResponseAdapter implements WebServiceContainer.Response {
271            private final HttpResponse response;
272    
273            public ResponseAdapter(HttpResponse response) {
274                this.response = response;
275            }
276    
277            public void setHeader(String name, String value) {
278                response.setField(name, value);
279            }
280    
281            public String getHeader(String name) {
282                return response.getField(name);
283            }
284    
285            public OutputStream getOutputStream() {
286                return response.getOutputStream();
287            }
288    
289            public void setStatusCode(int code) {
290                response.setStatus(code);
291            }
292    
293            public int getStatusCode() {
294                return response.getStatus();
295            }
296    
297            public void setContentType(String type) {
298                response.setContentType(type);
299            }
300    
301            public String getContentType() {
302                return response.getContentType();
303            }
304    
305            public void setStatusMessage(String responseString) {
306                response.setStatus(response.getStatus(), responseString);
307            }
308        }
309    
310    }