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.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.servlet;
21 import java.io.IOException;
22 import java.lang.reflect.Method;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.List;
32 import java.util.Stack;
34 import javax.servlet.MultipartConfigElement;
35 import javax.servlet.Servlet;
36 import javax.servlet.ServletConfig;
37 import javax.servlet.ServletContext;
38 import javax.servlet.ServletException;
39 import javax.servlet.ServletRegistration;
40 import javax.servlet.ServletRequest;
41 import javax.servlet.ServletResponse;
42 import javax.servlet.ServletSecurityElement;
43 import javax.servlet.SingleThreadModel;
44 import javax.servlet.UnavailableException;
46 import org.eclipse.jetty.security.IdentityService;
47 import org.eclipse.jetty.security.RunAsToken;
48 import org.eclipse.jetty.server.Request;
49 import org.eclipse.jetty.server.UserIdentity;
50 import org.eclipse.jetty.server.handler.ContextHandler;
51 import org.eclipse.jetty.util.Loader;
52 import org.eclipse.jetty.util.annotation.ManagedAttribute;
53 import org.eclipse.jetty.util.annotation.ManagedObject;
54 import org.eclipse.jetty.util.log.Log;
55 import org.eclipse.jetty.util.log.Logger;
60 /* --------------------------------------------------------------------- */
61 /** Servlet Instance and Context Holder.
62 * Holds the name, params and some state of a javax.servlet.Servlet
63 * instance. It implements the ServletConfig interface.
64 * This class will organise the loading of the servlet when needed or
69 @ManagedObject("Servlet Holder")
70 public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope, Comparable<ServletHolder>
72 private static final Logger LOG = Log.getLogger(ServletHolder.class);
74 /* ---------------------------------------------------------------- */
75 private int _initOrder = -1;
76 private boolean _initOnStartup=false;
77 private Map<String, String> _roleMap;
78 private String _forcedPath;
79 private String _runAsRole;
80 private RunAsToken _runAsToken;
81 private IdentityService _identityService;
82 private ServletRegistration.Dynamic _registration;
85 private transient Servlet _servlet;
86 private transient Config _config;
87 private transient long _unavailable;
88 private transient boolean _enabled = true;
89 private transient UnavailableException _unavailableEx;
91 public static final String JSP_GENERATED_PACKAGE_NAME = "org.eclipse.jetty.servlet.jspPackagePrefix";
92 public static final Map<String,String> NO_MAPPED_ROLES = Collections.emptyMap();
94 /* ---------------------------------------------------------------- */
97 public ServletHolder()
99 this(Source.EMBEDDED);
102 /* ---------------------------------------------------------------- */
105 public ServletHolder(Holder.Source creator)
110 /* ---------------------------------------------------------------- */
111 /** Constructor for existing servlet.
113 public ServletHolder(Servlet servlet)
115 this(Source.EMBEDDED);
119 /* ---------------------------------------------------------------- */
120 /** Constructor for servlet class.
122 public ServletHolder(String name, Class<? extends Servlet> servlet)
124 this(Source.EMBEDDED);
126 setHeldClass(servlet);
129 /* ---------------------------------------------------------------- */
130 /** Constructor for servlet class.
132 public ServletHolder(String name, Servlet servlet)
134 this(Source.EMBEDDED);
139 /* ---------------------------------------------------------------- */
140 /** Constructor for servlet class.
142 public ServletHolder(Class<? extends Servlet> servlet)
144 this(Source.EMBEDDED);
145 setHeldClass(servlet);
148 /* ---------------------------------------------------------------- */
150 * @return The unavailable exception or null if not unavailable
152 public UnavailableException getUnavailableException()
154 return _unavailableEx;
157 /* ------------------------------------------------------------ */
158 public synchronized void setServlet(Servlet servlet)
160 if (servlet==null || servlet instanceof SingleThreadModel)
161 throw new IllegalArgumentException();
165 setHeldClass(servlet.getClass());
167 setName(servlet.getClass().getName()+"-"+super.hashCode());
170 /* ------------------------------------------------------------ */
171 @ManagedAttribute(value="initialization order", readonly=true)
172 public int getInitOrder()
177 /* ------------------------------------------------------------ */
178 /** Set the initialize order.
179 * Holders with order<0, are initialized on use. Those with
180 * order>=0 are initialized in increasing order when the handler
183 public void setInitOrder(int order)
185 _initOnStartup=order>=0;
189 /* ------------------------------------------------------------ */
190 /** Comparitor by init order.
193 public int compareTo(ServletHolder sh)
197 if (sh._initOrder<_initOrder)
199 if (sh._initOrder>_initOrder)
202 int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0;
204 c=_name.compareTo(sh._name);
208 /* ------------------------------------------------------------ */
209 public boolean equals(Object o)
211 return o instanceof ServletHolder && compareTo((ServletHolder)o)==0;
214 /* ------------------------------------------------------------ */
215 public int hashCode()
217 return _name==null?System.identityHashCode(this):_name.hashCode();
220 /* ------------------------------------------------------------ */
221 /** Link a user role.
222 * Translate the role name used by a servlet, to the link name
223 * used by the container.
224 * @param name The role name as used by the servlet
225 * @param link The role name as used by the container.
227 public synchronized void setUserRoleLink(String name,String link)
230 _roleMap=new HashMap<String, String>();
231 _roleMap.put(name,link);
234 /* ------------------------------------------------------------ */
235 /** get a user role link.
236 * @param name The name of the role
237 * @return The name as translated by the link. If no link exists,
238 * the name is returned.
240 public String getUserRoleLink(String name)
244 String link= _roleMap.get(name);
245 return (link==null)?name:link;
248 /* ------------------------------------------------------------ */
250 * @return Returns the forcedPath.
252 @ManagedAttribute(value="forced servlet path", readonly=true)
253 public String getForcedPath()
258 /* ------------------------------------------------------------ */
260 * @param forcedPath The forcedPath to set.
262 public void setForcedPath(String forcedPath)
264 _forcedPath = forcedPath;
267 public boolean isEnabled()
273 public void setEnabled(boolean enabled)
279 /* ------------------------------------------------------------ */
280 public void doStart()
287 // Handle JSP file forced paths
288 if (_forcedPath != null)
290 // Look for a precompiled JSP Servlet
291 String precompiled=getClassNameForJsp(_forcedPath);
292 LOG.debug("Checking for precompiled servlet {} for jsp {}", precompiled, _forcedPath);
293 ServletHolder jsp=getServletHandler().getServlet(precompiled);
296 LOG.debug("JSP file {} for {} mapped to Servlet {}",_forcedPath, getName(),jsp.getClassName());
297 // set the className for this servlet to the precompiled one
298 setClassName(jsp.getClassName());
302 if (getClassName() == null)
304 // Look for normal JSP servlet
305 jsp=getServletHandler().getServlet("jsp");
308 LOG.debug("JSP file {} for {} mapped to Servlet {}",_forcedPath, getName(),jsp.getClassName());
309 setClassName(jsp.getClassName());
310 //copy jsp init params that don't exist for this servlet
311 for (Map.Entry<String, String> entry:jsp.getInitParameters().entrySet())
313 if (!_initParams.containsKey(entry.getKey()))
314 setInitParameter(entry.getKey(), entry.getValue());
316 //jsp specific: set up the jsp-file on the JspServlet so it can precompile iff load-on-startup is >=0
318 setInitParameter("jspFile", _forcedPath);
325 //check servlet has a class (ie is not a preliminary registration). If preliminary, fail startup.
330 catch (UnavailableException ue)
333 if (_servletHandler.isStartWithUnavailable())
343 //servlet is not an instance of javax.servlet.Servlet
348 catch (UnavailableException ue)
351 if (_servletHandler.isStartWithUnavailable())
360 //check if we need to forcibly set load-on-startup
361 checkInitOnStartup();
363 _identityService = _servletHandler.getIdentityService();
364 if (_identityService!=null && _runAsRole!=null)
365 _runAsToken=_identityService.newRunAsToken(_runAsRole);
367 _config=new Config();
369 if (_class!=null && javax.servlet.SingleThreadModel.class.isAssignableFrom(_class))
370 _servlet = new SingleThreadedWrapper();
375 /* ------------------------------------------------------------ */
377 public void initialize ()
381 if (_extInstance || _initOnStartup)
389 if (_servletHandler.isStartWithUnavailable())
398 /* ------------------------------------------------------------ */
402 Object old_run_as = null;
407 if (_identityService!=null)
408 old_run_as=_identityService.setRunAs(_identityService.getSystemUserIdentity(),_runAsToken);
410 destroyInstance(_servlet);
418 if (_identityService!=null)
419 _identityService.unsetRunAs(old_run_as);
429 /* ------------------------------------------------------------ */
431 public void destroyInstance (Object o)
436 Servlet servlet = ((Servlet)o);
437 getServletHandler().destroyServlet(servlet);
441 /* ------------------------------------------------------------ */
443 * @return The servlet
445 public synchronized Servlet getServlet()
446 throws ServletException
448 // Handle previous unavailability
451 if (_unavailable<0 || _unavailable>0 && System.currentTimeMillis()<_unavailable)
452 throw _unavailableEx;
462 /* ------------------------------------------------------------ */
463 /** Get the servlet instance (no initialization done).
464 * @return The servlet or null
466 public Servlet getServletInstance()
471 /* ------------------------------------------------------------ */
473 * Check to ensure class of servlet is acceptable.
474 * @throws UnavailableException
476 public void checkServletType ()
477 throws UnavailableException
479 if (_class==null || !javax.servlet.Servlet.class.isAssignableFrom(_class))
481 throw new UnavailableException("Servlet "+_class+" is not a javax.servlet.Servlet");
485 /* ------------------------------------------------------------ */
487 * @return true if the holder is started and is not unavailable
489 public boolean isAvailable()
491 if (isStarted()&& _unavailable==0)
502 return isStarted()&& _unavailable==0;
505 /* ------------------------------------------------------------ */
507 * Check if there is a javax.servlet.annotation.ServletSecurity
508 * annotation on the servlet class. If there is, then we force
509 * it to be loaded on startup, because all of the security
510 * constraints must be calculated as the container starts.
513 private void checkInitOnStartup()
518 if ((_class.getAnnotation(javax.servlet.annotation.ServletSecurity.class) != null) && !_initOnStartup)
519 setInitOrder(Integer.MAX_VALUE);
522 /* ------------------------------------------------------------ */
523 private void makeUnavailable(UnavailableException e)
525 if (_unavailableEx==e && _unavailable!=0)
528 _servletHandler.getServletContext().log("unavailable",e);
536 if (_unavailableEx.getUnavailableSeconds()>0)
537 _unavailable=System.currentTimeMillis()+1000*_unavailableEx.getUnavailableSeconds();
539 _unavailable=System.currentTimeMillis()+5000; // TODO configure
543 /* ------------------------------------------------------------ */
545 private void makeUnavailable(final Throwable e)
547 if (e instanceof UnavailableException)
548 makeUnavailable((UnavailableException)e);
551 ServletContext ctx = _servletHandler.getServletContext();
553 LOG.info("unavailable",e);
555 ctx.log("unavailable",e);
556 _unavailableEx=new UnavailableException(String.valueOf(e),-1)
566 /* ------------------------------------------------------------ */
567 private void initServlet()
568 throws ServletException
570 Object old_run_as = null;
574 _servlet=newInstance();
576 _config=new Config();
581 if (_identityService!=null)
583 old_run_as=_identityService.setRunAs(_identityService.getSystemUserIdentity(),_runAsToken);
586 // Handle configuring servlets that implement org.apache.jasper.servlet.JspServlet
594 LOG.debug("Filter.init {}",_servlet);
595 _servlet.init(_config);
597 catch (UnavailableException e)
604 catch (ServletException e)
606 makeUnavailable(e.getCause()==null?e:e.getCause());
616 throw new ServletException(this.toString(),e);
621 if (_identityService!=null)
622 _identityService.unsetRunAs(old_run_as);
627 /* ------------------------------------------------------------ */
631 protected void initJspServlet () throws Exception
633 ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext());
635 /* Set the webapp's classpath for Jasper */
636 ch.setAttribute("org.apache.catalina.jsp_classpath", ch.getClassPath());
638 /* Set the system classpath for Jasper */
639 setInitParameter("com.sun.appserv.jsp.classpath", Loader.getClassPath(ch.getClassLoader().getParent()));
641 /* Set up other classpath attribute */
642 if ("?".equals(getInitParameter("classpath")))
644 String classpath = ch.getClassPath();
645 LOG.debug("classpath=" + classpath);
646 if (classpath != null)
647 setInitParameter("classpath", classpath);
651 /* ------------------------------------------------------------ */
653 * Register a ServletRequestListener that will ensure tmp multipart
654 * files are deleted when the request goes out of scope.
658 protected void initMultiPart () throws Exception
660 //if this servlet can handle multipart requests, ensure tmp files will be
661 //cleaned up correctly
662 if (((Registration)getRegistration()).getMultipartConfig() != null)
664 //Register a listener to delete tmp files that are created as a result of this
665 //servlet calling Request.getPart() or Request.getParts()
667 ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext());
668 ch.addEventListener(new Request.MultiPartCleanerListener());
672 /* ------------------------------------------------------------ */
674 * @see org.eclipse.jetty.server.UserIdentity.Scope#getContextPath()
677 public String getContextPath()
679 return _config.getServletContext().getContextPath();
682 /* ------------------------------------------------------------ */
684 * @see org.eclipse.jetty.server.UserIdentity.Scope#getRoleRefMap()
687 public Map<String, String> getRoleRefMap()
692 /* ------------------------------------------------------------ */
693 @ManagedAttribute(value="role to run servlet as", readonly=true)
694 public String getRunAsRole()
699 /* ------------------------------------------------------------ */
700 public void setRunAsRole(String role)
705 /* ------------------------------------------------------------ */
706 /** Service a request with this servlet.
708 public void handle(Request baseRequest,
709 ServletRequest request,
710 ServletResponse response)
711 throws ServletException,
712 UnavailableException,
716 throw new UnavailableException("Servlet Not Initialized");
718 Servlet servlet=_servlet;
722 throw new UnavailableException("Servlet not initialized", -1);
723 if (_unavailable!=0 || (!_initOnStartup && servlet==null))
724 servlet=getServlet();
726 throw new UnavailableException("Could not instantiate "+_class);
729 // Service the request
730 boolean servlet_error=true;
731 Object old_run_as = null;
732 boolean suspendable = baseRequest.isAsyncSupported();
735 // Handle aliased path
736 if (_forcedPath!=null)
737 // TODO complain about poor naming to the Jasper folks
738 request.setAttribute("org.apache.catalina.jsp_file",_forcedPath);
741 if (_identityService!=null)
742 old_run_as=_identityService.setRunAs(baseRequest.getResolvedUserIdentity(),_runAsToken);
744 if (!isAsyncSupported())
745 baseRequest.setAsyncSupported(false);
747 MultipartConfigElement mpce = ((Registration)getRegistration()).getMultipartConfig();
749 request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
751 servlet.service(request,response);
754 catch(UnavailableException e)
757 throw _unavailableEx;
761 baseRequest.setAsyncSupported(suspendable);
764 if (_identityService!=null)
765 _identityService.unsetRunAs(old_run_as);
767 // Handle error params.
769 request.setAttribute("javax.servlet.error.servlet_name",getName());
774 /* ------------------------------------------------------------ */
775 private boolean isJspServlet ()
777 if (_servlet == null)
780 Class c = _servlet.getClass();
782 boolean result = false;
783 while (c != null && !result)
785 result = isJspServlet(c.getName());
786 c = c.getSuperclass();
793 /* ------------------------------------------------------------ */
794 private boolean isJspServlet (String classname)
796 if (classname == null)
798 return ("org.apache.jasper.servlet.JspServlet".equals(classname));
802 /* ------------------------------------------------------------ */
803 private String getNameOfJspClass (String jsp)
808 int i = jsp.lastIndexOf('/') + 1;
809 jsp = jsp.substring(i);
812 Class jspUtil = Loader.loadClass(Holder.class, "org.apache.jasper.compiler.JspUtil");
813 Method makeJavaIdentifier = jspUtil.getMethod("makeJavaIdentifier", String.class);
814 return (String)makeJavaIdentifier.invoke(null, jsp);
818 String tmp = jsp.replace('.','_');
819 LOG.warn("Unable to make identifier for jsp "+jsp +" trying "+tmp+" instead");
820 if (LOG.isDebugEnabled())
827 /* ------------------------------------------------------------ */
828 private String getPackageOfJspClass (String jsp)
833 int i = jsp.lastIndexOf('/');
838 Class jspUtil = Loader.loadClass(Holder.class, "org.apache.jasper.compiler.JspUtil");
839 Method makeJavaPackage = jspUtil.getMethod("makeJavaPackage", String.class);
840 return (String)makeJavaPackage.invoke(null, jsp.substring(0,i));
844 String tmp = jsp.substring(1).replace('/','.');
845 LOG.warn("Unable to make package for jsp "+jsp +" trying "+tmp+" instead");
846 if (LOG.isDebugEnabled())
853 /* ------------------------------------------------------------ */
854 private String getJspPackagePrefix ()
856 String jspPackageName = (String)getServletHandler().getServletContext().getInitParameter(JSP_GENERATED_PACKAGE_NAME );
857 if (jspPackageName == null)
858 jspPackageName = "org.apache.jsp";
860 return jspPackageName;
864 /* ------------------------------------------------------------ */
865 private String getClassNameForJsp (String jsp)
870 return getJspPackagePrefix() + "." +getPackageOfJspClass(jsp) + "." + getNameOfJspClass(jsp);
874 /* ------------------------------------------------------------ */
875 /* ------------------------------------------------------------ */
876 /* ------------------------------------------------------------ */
877 protected class Config extends HolderConfig implements ServletConfig
879 /* -------------------------------------------------------- */
881 public String getServletName()
888 /* -------------------------------------------------------- */
889 /* -------------------------------------------------------- */
890 /* -------------------------------------------------------- */
891 public class Registration extends HolderRegistration implements ServletRegistration.Dynamic
893 protected MultipartConfigElement _multipartConfig;
896 public Set<String> addMapping(String... urlPatterns)
898 illegalStateIfContextStarted();
899 Set<String> clash=null;
900 for (String pattern : urlPatterns)
902 ServletMapping mapping = _servletHandler.getServletMapping(pattern);
905 //if the servlet mapping was from a default descriptor, then allow it to be overridden
906 if (!mapping.isDefault())
909 clash=new HashSet<String>();
915 //if there were any clashes amongst the urls, return them
919 //otherwise apply all of them
920 ServletMapping mapping = new ServletMapping();
921 mapping.setServletName(ServletHolder.this.getName());
922 mapping.setPathSpecs(urlPatterns);
923 _servletHandler.addServletMapping(mapping);
925 return Collections.emptySet();
929 public Collection<String> getMappings()
931 ServletMapping[] mappings =_servletHandler.getServletMappings();
932 List<String> patterns=new ArrayList<String>();
935 for (ServletMapping mapping : mappings)
937 if (!mapping.getServletName().equals(getName()))
939 String[] specs=mapping.getPathSpecs();
940 if (specs!=null && specs.length>0)
941 patterns.addAll(Arrays.asList(specs));
948 public String getRunAsRole()
954 public void setLoadOnStartup(int loadOnStartup)
956 illegalStateIfContextStarted();
957 ServletHolder.this.setInitOrder(loadOnStartup);
960 public int getInitOrder()
962 return ServletHolder.this.getInitOrder();
966 public void setMultipartConfig(MultipartConfigElement element)
968 _multipartConfig = element;
971 public MultipartConfigElement getMultipartConfig()
973 return _multipartConfig;
977 public void setRunAsRole(String role)
983 public Set<String> setServletSecurity(ServletSecurityElement securityElement)
985 return _servletHandler.setServletSecurity(this, securityElement);
989 public ServletRegistration.Dynamic getRegistration()
991 if (_registration == null)
992 _registration = new Registration();
993 return _registration;
996 /* -------------------------------------------------------- */
997 /* -------------------------------------------------------- */
998 /* -------------------------------------------------------- */
999 private class SingleThreadedWrapper implements Servlet
1001 Stack<Servlet> _stack=new Stack<Servlet>();
1004 public void destroy()
1008 while(_stack.size()>0)
1009 try { (_stack.pop()).destroy(); } catch (Exception e) { LOG.warn(e); }
1014 public ServletConfig getServletConfig()
1020 public String getServletInfo()
1026 public void init(ServletConfig config) throws ServletException
1030 if(_stack.size()==0)
1034 Servlet s = newInstance();
1038 catch (ServletException e)
1044 throw new ServletException(e);
1051 public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
1057 s=(Servlet)_stack.pop();
1065 catch (ServletException e)
1071 throw new ServletException(e);
1090 /* ------------------------------------------------------------ */
1092 * @return the newly created Servlet instance
1093 * @throws ServletException
1094 * @throws IllegalAccessException
1095 * @throws InstantiationException
1097 protected Servlet newInstance() throws ServletException, IllegalAccessException, InstantiationException
1101 ServletContext ctx = getServletHandler().getServletContext();
1102 if (ctx instanceof ServletContextHandler.Context)
1103 return ((ServletContextHandler.Context)ctx).createServlet(getHeldClass());
1104 return getHeldClass().newInstance();
1106 catch (ServletException se)
1108 Throwable cause = se.getRootCause();
1109 if (cause instanceof InstantiationException)
1110 throw (InstantiationException)cause;
1111 if (cause instanceof IllegalAccessException)
1112 throw (IllegalAccessException)cause;
1118 /* ------------------------------------------------------------ */
1120 public String toString()
1122 return String.format("%s@%x==%s,%d,%b",_name,hashCode(),_className,_initOrder,_servlet!=null);