1//
2//  ========================================================================
3//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4//  ------------------------------------------------------------------------
5//  All rights reserved. This program and the accompanying materials
6//  are made available under the terms of the Eclipse Public License v1.0
7//  and Apache License v2.0 which accompanies this distribution.
8//
9//      The Eclipse Public License is available at
10//      http://www.eclipse.org/legal/epl-v10.html
11//
12//      The Apache License v2.0 is available at
13//      http://www.opensource.org/licenses/apache2.0.php
14//
15//  You may elect to redistribute this code under either of these licenses.
16//  ========================================================================
17//
18
19package org.eclipse.jetty.servlet;
20
21import java.io.IOException;
22import java.util.Enumeration;
23import java.util.HashMap;
24import java.util.Locale;
25import java.util.Map;
26
27import javax.servlet.ServletContext;
28import javax.servlet.ServletException;
29import javax.servlet.UnavailableException;
30import javax.servlet.http.HttpServlet;
31import javax.servlet.http.HttpServletRequest;
32import javax.servlet.http.HttpServletRequestWrapper;
33import javax.servlet.http.HttpServletResponse;
34
35import org.eclipse.jetty.server.AbstractHttpConnection;
36import org.eclipse.jetty.server.Dispatcher;
37import org.eclipse.jetty.server.Handler;
38import org.eclipse.jetty.server.Request;
39import org.eclipse.jetty.server.handler.ContextHandler;
40import org.eclipse.jetty.server.handler.HandlerWrapper;
41import org.eclipse.jetty.util.LazyList;
42import org.eclipse.jetty.util.URIUtil;
43import org.eclipse.jetty.util.log.Log;
44import org.eclipse.jetty.util.log.Logger;
45
46/* ------------------------------------------------------------ */
47/**  Dynamic Servlet Invoker.
48 * This servlet invokes anonymous servlets that have not been defined
49 * in the web.xml or by other means. The first element of the pathInfo
50 * of a request passed to the envoker is treated as a servlet name for
51 * an existing servlet, or as a class name of a new servlet.
52 * This servlet is normally mapped to /servlet/*
53 * This servlet support the following initParams:
54 * <PRE>
55 *  nonContextServlets       If false, the invoker can only load
56 *                           servlets from the contexts classloader.
57 *                           This is false by default and setting this
58 *                           to true may have security implications.
59 *
60 *  verbose                  If true, log dynamic loads
61 *
62 *  *                        All other parameters are copied to the
63 *                           each dynamic servlet as init parameters
64 * </PRE>
65 * @version $Id: Invoker.java 4780 2009-03-17 15:36:08Z jesse $
66 *
67 */
68public class Invoker extends HttpServlet
69{
70    private static final Logger LOG = Log.getLogger(Invoker.class);
71
72
73    private ContextHandler _contextHandler;
74    private ServletHandler _servletHandler;
75    private Map.Entry _invokerEntry;
76    private Map _parameters;
77    private boolean _nonContextServlets;
78    private boolean _verbose;
79
80    /* ------------------------------------------------------------ */
81    public void init()
82    {
83        ServletContext config=getServletContext();
84        _contextHandler=((ContextHandler.Context)config).getContextHandler();
85
86        Handler handler=_contextHandler.getHandler();
87        while (handler!=null && !(handler instanceof ServletHandler) && (handler instanceof HandlerWrapper))
88            handler=((HandlerWrapper)handler).getHandler();
89        _servletHandler = (ServletHandler)handler;
90        Enumeration e = getInitParameterNames();
91        while(e.hasMoreElements())
92        {
93            String param=(String)e.nextElement();
94            String value=getInitParameter(param);
95            String lvalue=value.toLowerCase(Locale.ENGLISH);
96            if ("nonContextServlets".equals(param))
97            {
98                _nonContextServlets=value.length()>0 && lvalue.startsWith("t");
99            }
100            if ("verbose".equals(param))
101            {
102                _verbose=value.length()>0 && lvalue.startsWith("t");
103            }
104            else
105            {
106                if (_parameters==null)
107                    _parameters=new HashMap();
108                _parameters.put(param,value);
109            }
110        }
111    }
112
113    /* ------------------------------------------------------------ */
114    protected void service(HttpServletRequest request, HttpServletResponse response)
115	throws ServletException, IOException
116    {
117        // Get the requested path and info
118        boolean included=false;
119        String servlet_path=(String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH);
120        if (servlet_path==null)
121            servlet_path=request.getServletPath();
122        else
123            included=true;
124        String path_info = (String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
125        if (path_info==null)
126            path_info=request.getPathInfo();
127
128        // Get the servlet class
129        String servlet = path_info;
130        if (servlet==null || servlet.length()<=1 )
131        {
132            response.sendError(404);
133            return;
134        }
135
136
137        int i0=servlet.charAt(0)=='/'?1:0;
138        int i1=servlet.indexOf('/',i0);
139        servlet=i1<0?servlet.substring(i0):servlet.substring(i0,i1);
140
141        // look for a named holder
142        ServletHolder[] holders = _servletHandler.getServlets();
143        ServletHolder holder = getHolder (holders, servlet);
144
145        if (holder!=null)
146        {
147            // Found a named servlet (from a user's web.xml file) so
148            // now we add a mapping for it
149            if (LOG.isDebugEnabled())
150                LOG.debug("Adding servlet mapping for named servlet:"+servlet+":"+URIUtil.addPaths(servlet_path,servlet)+"/*");
151            ServletMapping mapping = new ServletMapping();
152            mapping.setServletName(servlet);
153            mapping.setPathSpec(URIUtil.addPaths(servlet_path,servlet)+"/*");
154            _servletHandler.setServletMappings((ServletMapping[])LazyList.addToArray(_servletHandler.getServletMappings(), mapping, ServletMapping.class));
155        }
156        else
157        {
158            // look for a class mapping
159            if (servlet.endsWith(".class"))
160                servlet=servlet.substring(0,servlet.length()-6);
161            if (servlet==null || servlet.length()==0)
162            {
163                response.sendError(404);
164                return;
165            }
166
167            synchronized(_servletHandler)
168            {
169                // find the entry for the invoker (me)
170                 _invokerEntry=_servletHandler.getHolderEntry(servlet_path);
171
172                // Check for existing mapping (avoid threaded race).
173                String path=URIUtil.addPaths(servlet_path,servlet);
174                Map.Entry entry = _servletHandler.getHolderEntry(path);
175
176                if (entry!=null && !entry.equals(_invokerEntry))
177                {
178                    // Use the holder
179                    holder=(ServletHolder)entry.getValue();
180                }
181                else
182                {
183                    // Make a holder
184                    if (LOG.isDebugEnabled())
185                        LOG.debug("Making new servlet="+servlet+" with path="+path+"/*");
186                    holder=_servletHandler.addServletWithMapping(servlet, path+"/*");
187
188                    if (_parameters!=null)
189                        holder.setInitParameters(_parameters);
190
191                    try {holder.start();}
192                    catch (Exception e)
193                    {
194                        LOG.debug(e);
195                        throw new UnavailableException(e.toString());
196                    }
197
198                    // Check it is from an allowable classloader
199                    if (!_nonContextServlets)
200                    {
201                        Object s=holder.getServlet();
202
203                        if (_contextHandler.getClassLoader()!=
204                            s.getClass().getClassLoader())
205                        {
206                            try
207                            {
208                                holder.stop();
209                            }
210                            catch (Exception e)
211                            {
212                                LOG.ignore(e);
213                            }
214
215                            LOG.warn("Dynamic servlet "+s+
216                                         " not loaded from context "+
217                                         request.getContextPath());
218                            throw new UnavailableException("Not in context");
219                        }
220                    }
221
222                    if (_verbose && LOG.isDebugEnabled())
223                        LOG.debug("Dynamic load '"+servlet+"' at "+path);
224                }
225            }
226        }
227
228        if (holder!=null)
229        {
230            final Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
231            holder.handle(baseRequest,
232                    new InvokedRequest(request,included,servlet,servlet_path,path_info),
233                          response);
234        }
235        else
236        {
237            LOG.info("Can't find holder for servlet: "+servlet);
238            response.sendError(404);
239        }
240
241
242    }
243
244    /* ------------------------------------------------------------ */
245    class InvokedRequest extends HttpServletRequestWrapper
246    {
247        String _servletPath;
248        String _pathInfo;
249        boolean _included;
250
251        /* ------------------------------------------------------------ */
252        InvokedRequest(HttpServletRequest request,
253                boolean included,
254                String name,
255                String servletPath,
256                String pathInfo)
257        {
258            super(request);
259            _included=included;
260            _servletPath=URIUtil.addPaths(servletPath,name);
261            _pathInfo=pathInfo.substring(name.length()+1);
262            if (_pathInfo.length()==0)
263                _pathInfo=null;
264        }
265
266        /* ------------------------------------------------------------ */
267        public String getServletPath()
268        {
269            if (_included)
270                return super.getServletPath();
271            return _servletPath;
272        }
273
274        /* ------------------------------------------------------------ */
275        public String getPathInfo()
276        {
277            if (_included)
278                return super.getPathInfo();
279            return _pathInfo;
280        }
281
282        /* ------------------------------------------------------------ */
283        public Object getAttribute(String name)
284        {
285            if (_included)
286            {
287                if (name.equals(Dispatcher.INCLUDE_REQUEST_URI))
288                    return URIUtil.addPaths(URIUtil.addPaths(getContextPath(),_servletPath),_pathInfo);
289                if (name.equals(Dispatcher.INCLUDE_PATH_INFO))
290                    return _pathInfo;
291                if (name.equals(Dispatcher.INCLUDE_SERVLET_PATH))
292                    return _servletPath;
293            }
294            return super.getAttribute(name);
295        }
296    }
297
298
299    private ServletHolder getHolder(ServletHolder[] holders, String servlet)
300    {
301        if (holders == null)
302            return null;
303
304        ServletHolder holder = null;
305        for (int i=0; holder==null && i<holders.length; i++)
306        {
307            if (holders[i].getName().equals(servlet))
308            {
309                holder = holders[i];
310            }
311        }
312        return holder;
313    }
314}
315