View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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  
19  package org.eclipse.jetty.annotations;
20  
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.List;
24  
25  import javax.servlet.Servlet;
26  import javax.servlet.annotation.WebInitParam;
27  import javax.servlet.annotation.WebServlet;
28  import javax.servlet.http.HttpServlet;
29  
30  import org.eclipse.jetty.servlet.Holder;
31  import org.eclipse.jetty.servlet.ServletHolder;
32  import org.eclipse.jetty.servlet.ServletMapping;
33  import org.eclipse.jetty.util.ArrayUtil;
34  import org.eclipse.jetty.util.LazyList;
35  import org.eclipse.jetty.util.log.Log;
36  import org.eclipse.jetty.util.log.Logger;
37  import org.eclipse.jetty.util.resource.Resource;
38  import org.eclipse.jetty.webapp.DiscoveredAnnotation;
39  import org.eclipse.jetty.webapp.MetaData;
40  import org.eclipse.jetty.webapp.Origin;
41  import org.eclipse.jetty.webapp.WebAppContext;
42  
43  /**
44   * WebServletAnnotation
45   *
46   *
47   */
48  public class WebServletAnnotation extends DiscoveredAnnotation
49  {
50      private static final Logger LOG = Log.getLogger(WebServletAnnotation.class);
51  
52      public WebServletAnnotation (WebAppContext context, String className)
53      {
54          super(context, className);
55      }
56  
57  
58      public WebServletAnnotation (WebAppContext context, String className, Resource resource)
59      {
60          super(context, className, resource);
61      }
62  
63      /**
64       * @see DiscoveredAnnotation#apply()
65       */
66      public void apply()
67      {
68          //TODO check this algorithm with new rules for applying descriptors and annotations in order
69          Class<? extends Servlet> clazz = (Class<? extends Servlet>)getTargetClass();
70  
71          if (clazz == null)
72          {
73              LOG.warn(_className+" cannot be loaded");
74              return;
75          }
76  
77          //Servlet Spec 8.1.1
78          if (!HttpServlet.class.isAssignableFrom(clazz))
79          {
80              LOG.warn(clazz.getName()+" is not assignable from javax.servlet.http.HttpServlet");
81              return;
82          }
83  
84          WebServlet annotation = (WebServlet)clazz.getAnnotation(WebServlet.class);
85  
86          if (annotation.urlPatterns().length > 0 && annotation.value().length > 0)
87          {
88              LOG.warn(clazz.getName()+ " defines both @WebServlet.value and @WebServlet.urlPatterns");
89              return;
90          }
91  
92          String[] urlPatterns = annotation.value();
93          if (urlPatterns.length == 0)
94              urlPatterns = annotation.urlPatterns();
95  
96          if (urlPatterns.length == 0)
97          {
98              LOG.warn(clazz.getName()+ " defines neither @WebServlet.value nor @WebServlet.urlPatterns");
99              return;
100         }
101 
102         //canonicalize the patterns
103         ArrayList<String> urlPatternList = new ArrayList<String>();
104         for (String p : urlPatterns)
105             urlPatternList.add(Util.normalizePattern(p));
106 
107         String servletName = (annotation.name().equals("")?clazz.getName():annotation.name());
108 
109         MetaData metaData = _context.getMetaData();
110         ServletMapping mapping = null; //the new mapping
111 
112         //Find out if a <servlet> already exists with this name
113         ServletHolder[] holders = _context.getServletHandler().getServlets();
114 
115         ServletHolder holder = null;
116         if (holders != null)
117         {
118             for (ServletHolder h : holders)
119             {
120                 if (h.getName() != null && servletName.equals(h.getName()))
121                 {
122                     holder = h;
123                     break;
124                 }
125             }
126         }
127 
128         //handle creation/completion of a servlet
129         if (holder == null)
130         {
131             //No servlet of this name has already been defined, either by a descriptor
132             //or another annotation (which would be impossible).
133             holder = _context.getServletHandler().newServletHolder(Holder.Source.ANNOTATION);
134             holder.setHeldClass(clazz);
135             metaData.setOrigin(servletName+".servlet.servlet-class",annotation,clazz);
136 
137             holder.setName(servletName);
138             holder.setDisplayName(annotation.displayName());
139             metaData.setOrigin(servletName+".servlet.display-name",annotation,clazz);
140 
141             holder.setInitOrder(annotation.loadOnStartup());
142             metaData.setOrigin(servletName+".servlet.load-on-startup",annotation,clazz);
143 
144             holder.setAsyncSupported(annotation.asyncSupported());
145             metaData.setOrigin(servletName+".servlet.async-supported",annotation,clazz);
146 
147             for (WebInitParam ip:annotation.initParams())
148             {
149                 holder.setInitParameter(ip.name(), ip.value());
150                 metaData.setOrigin(servletName+".servlet.init-param."+ip.name(),ip,clazz);
151             }
152 
153             _context.getServletHandler().addServlet(holder);
154 
155 
156             mapping = new ServletMapping();
157             mapping.setServletName(holder.getName());
158             mapping.setPathSpecs( LazyList.toStringArray(urlPatternList));
159         }
160         else
161         {
162             //set the class according to the servlet that is annotated, if it wasn't already
163             //NOTE: this may be considered as "completing" an incomplete servlet registration, and it is
164             //not clear from servlet 3.0 spec whether this is intended, or if only a ServletContext.addServlet() call
165             //can complete it, see http://java.net/jira/browse/SERVLET_SPEC-42
166             if (holder.getClassName() == null)
167                 holder.setClassName(clazz.getName());
168             if (holder.getHeldClass() == null)
169                 holder.setHeldClass(clazz);
170 
171             //check if the existing servlet has each init-param from the annotation
172             //if not, add it
173             for (WebInitParam ip:annotation.initParams())
174             {
175                 if (metaData.getOrigin(servletName+".servlet.init-param."+ip.name())==Origin.NotSet)
176                 {
177                     holder.setInitParameter(ip.name(), ip.value());
178                     metaData.setOrigin(servletName+".servlet.init-param."+ip.name(),ip,clazz);
179                 }
180             }
181 
182 
183             //check the url-patterns
184             //ServletSpec 3.0 p81 If a servlet already has url mappings from a
185             //webxml or fragment descriptor the annotation is ignored.
186             //However, we want to be able to replace mappings that were given in webdefault.xml
187             List<ServletMapping> existingMappings = getServletMappingsForServlet(servletName);
188 
189             //if any mappings for this servlet already set by a descriptor that is not webdefault.xml forget
190             //about processing these url mappings
191             if (existingMappings.isEmpty() || !containsNonDefaultMappings(existingMappings))
192             {
193                 mapping = new ServletMapping();
194                 mapping.setServletName(servletName);
195                 mapping.setPathSpecs(LazyList.toStringArray(urlPatternList));
196             }
197         }
198 
199 
200         //We also want to be able to replace mappings that were defined in webdefault.xml
201         //that were for a different servlet eg a mapping in webdefault.xml for / to the jetty
202         //default servlet should be able to be replaced by an annotation for / to a different
203         //servlet
204         if (mapping != null)
205         {
206             //url mapping was permitted by annotation processing rules
207 
208             //take a copy of the existing servlet mappings that we can iterate over and remove from. This is
209             //because the ServletHandler interface does not support removal of individual mappings.
210             List<ServletMapping> allMappings = ArrayUtil.asMutableList(_context.getServletHandler().getServletMappings());
211 
212             //for each of the urls in the annotation, check if a mapping to same/different servlet exists
213             //  if mapping exists and is from a default descriptor, it can be replaced. NOTE: we do not
214             //  guard against duplicate path mapping here: that is the job of the ServletHandler
215             for (String p:urlPatternList)
216             {
217                 ServletMapping existingMapping = _context.getServletHandler().getServletMapping(p);
218                 if (existingMapping != null && existingMapping.isDefault())
219                 {
220                     String[] updatedPaths = ArrayUtil.removeFromArray(existingMapping.getPathSpecs(), p);
221                     //if we removed the last path from a servletmapping, delete the servletmapping
222                     if (updatedPaths == null || updatedPaths.length == 0)
223                     {
224                         boolean success = allMappings.remove(existingMapping);
225                         if (LOG.isDebugEnabled()) LOG.debug("Removed empty mapping {} from defaults descriptor success:{}",existingMapping, success);
226                     }
227                     else
228                     {
229                         existingMapping.setPathSpecs(updatedPaths);
230                         if (LOG.isDebugEnabled()) LOG.debug("Removed path {} from mapping {} from defaults descriptor ", p,existingMapping);
231                     }
232                 }
233                 _context.getMetaData().setOrigin(servletName+".servlet.mapping."+p, annotation, clazz);
234             }
235             allMappings.add(mapping);
236             _context.getServletHandler().setServletMappings(allMappings.toArray(new ServletMapping[allMappings.size()]));
237         }
238     }
239 
240 
241 
242 
243     /**
244      * @param name
245      * @return
246      */
247     private List<ServletMapping>  getServletMappingsForServlet (String name)
248     {
249         ServletMapping[] allMappings = _context.getServletHandler().getServletMappings();
250         if (allMappings == null)
251             return Collections.emptyList();
252 
253         List<ServletMapping> mappings = new ArrayList<ServletMapping>();
254         for (ServletMapping m:allMappings)
255         {
256             if (m.getServletName() != null && name.equals(m.getServletName()))
257             {
258                 mappings.add(m);
259             }
260         }
261         return mappings;
262     }
263 
264 
265     /**
266      * @param mappings
267      * @return
268      */
269     private boolean containsNonDefaultMappings (List<ServletMapping> mappings)
270     {
271         if (mappings == null)
272             return false;
273         for (ServletMapping m:mappings)
274         {
275             if (!m.isDefault())
276                 return true;
277         }
278         return false;
279     }
280 }