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: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
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    }