View Javadoc

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  
19  package org.eclipse.jetty.maven.plugin;
20  
21  import java.io.File;
22  import java.io.FileOutputStream;
23  import java.io.IOException;
24  import java.net.MalformedURLException;
25  import java.util.ArrayList;
26  import java.util.EventListener;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  import java.util.TreeSet;
32  
33  import org.eclipse.jetty.annotations.AnnotationConfiguration;
34  import org.eclipse.jetty.plus.webapp.EnvConfiguration;
35  import org.eclipse.jetty.plus.webapp.PlusConfiguration;
36  import org.eclipse.jetty.quickstart.PreconfigureDescriptorProcessor;
37  import org.eclipse.jetty.quickstart.QuickStartDescriptorGenerator;
38  import org.eclipse.jetty.servlet.FilterHolder;
39  import org.eclipse.jetty.servlet.FilterMapping;
40  import org.eclipse.jetty.servlet.ServletHolder;
41  import org.eclipse.jetty.servlet.ServletMapping;
42  import org.eclipse.jetty.util.URIUtil;
43  import org.eclipse.jetty.util.log.Log;
44  import org.eclipse.jetty.util.log.Logger;
45  import org.eclipse.jetty.util.resource.Resource;
46  import org.eclipse.jetty.util.resource.ResourceCollection;
47  import org.eclipse.jetty.webapp.Configuration;
48  import org.eclipse.jetty.webapp.FragmentConfiguration;
49  import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
50  import org.eclipse.jetty.webapp.MetaInfConfiguration;
51  import org.eclipse.jetty.webapp.WebAppContext;
52  import org.eclipse.jetty.webapp.WebInfConfiguration;
53  import org.eclipse.jetty.webapp.WebXmlConfiguration;
54  
55  /**
56   * JettyWebAppContext
57   * 
58   * Extends the WebAppContext to specialize for the maven environment.
59   * We pass in the list of files that should form the classpath for
60   * the webapp when executing in the plugin, and any jetty-env.xml file
61   * that may have been configured.
62   *
63   */
64  public class JettyWebAppContext extends WebAppContext
65  {
66      private static final Logger LOG = Log.getLogger(JettyWebAppContext.class);
67      
68     
69  
70      private static final String DEFAULT_CONTAINER_INCLUDE_JAR_PATTERN = ".*/javax.servlet-[^/]*\\.jar$|.*/servlet-api-[^/]*\\.jar$|.*javax.servlet.jsp.jstl-[^/]*\\.jar|.*taglibs-standard-impl-.*\\.jar";
71      private static final String WEB_INF_CLASSES_PREFIX = "/WEB-INF/classes";
72      private static final String WEB_INF_LIB_PREFIX = "/WEB-INF/lib";
73  
74      private final Configuration[] _defaultConfigurations = {
75                                                               new MavenWebInfConfiguration(),
76                                                               new WebXmlConfiguration(),
77                                                               new MetaInfConfiguration(),
78                                                               new FragmentConfiguration(),
79                                                               new EnvConfiguration(),
80                                                               new PlusConfiguration(),
81                                                               new AnnotationConfiguration(),
82                                                               new JettyWebXmlConfiguration()
83                                                              };
84  
85      private final Configuration[] _quickStartConfigurations = {
86                                                                  new MavenQuickStartConfiguration(),
87                                                                  new EnvConfiguration(),
88                                                                  new PlusConfiguration(),
89                                                                  new JettyWebXmlConfiguration()
90                                                                 };
91  
92      private File _classes = null;
93      private File _testClasses = null;
94      private final List<File> _webInfClasses = new ArrayList<File>();
95      private final List<File> _webInfJars = new ArrayList<File>();
96      private final Map<String, File> _webInfJarMap = new HashMap<String, File>();
97      private List<File> _classpathFiles;  //webInfClasses+testClasses+webInfJars
98      private String _jettyEnvXml;
99      private List<Overlay> _overlays;
100     private Resource _quickStartWebXml;
101     
102  
103     
104     /**
105      * Set the "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern" with a pattern for matching jars on
106      * container classpath to scan. This is analogous to the WebAppContext.setAttribute() call.
107      */
108     private String _containerIncludeJarPattern = null;
109     
110     /**
111      * Set the "org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern" with a pattern for matching jars on
112      * webapp's classpath to scan. This is analogous to the WebAppContext.setAttribute() call.
113      */
114     private String _webInfIncludeJarPattern = null;
115   
116 
117     
118     /**
119      * If there is no maven-war-plugin config for ordering of the current project in the
120      * sequence of overlays, use this to control whether the current project is added 
121      * first or last in list of overlaid resources
122      */
123     private boolean _baseAppFirst = true;
124 
125 
126 
127     private boolean _isGenerateQuickStart;
128     private PreconfigureDescriptorProcessor _preconfigProcessor;
129    
130 
131   
132 
133     public JettyWebAppContext ()
134     throws Exception
135     {
136         super();   
137         // Turn off copyWebInf option as it is not applicable for plugin.
138         super.setCopyWebInf(false);
139     }
140     public void setContainerIncludeJarPattern(String pattern)
141     {
142         _containerIncludeJarPattern = pattern;
143     }
144     
145     public String getContainerIncludeJarPattern()
146     {
147         return _containerIncludeJarPattern;
148     }
149     
150     
151     public String getWebInfIncludeJarPattern()
152     {
153         return _webInfIncludeJarPattern;
154     }
155     public void setWebInfIncludeJarPattern(String pattern)
156     {
157         _webInfIncludeJarPattern = pattern;
158     }
159    
160    
161     public List<File> getClassPathFiles()
162     {
163         return this._classpathFiles;
164     }
165     
166     
167     public void setJettyEnvXml (String jettyEnvXml)
168     {
169         this._jettyEnvXml = jettyEnvXml;
170     }
171     
172     public String getJettyEnvXml()
173     {
174         return this._jettyEnvXml;
175     }
176 
177    
178     public void setClasses(File dir)
179     {
180         _classes = dir;
181     }
182     
183     public File getClasses()
184     {
185         return _classes;
186     }
187     
188     public void setWebInfLib (List<File> jars)
189     {
190         _webInfJars.addAll(jars);
191     }
192     
193     
194     public void setTestClasses (File dir)
195     {
196         _testClasses = dir;
197     }
198     
199     
200     public File getTestClasses ()
201     {
202         return _testClasses;
203     }
204     
205     /**
206      * Ordered list of wars to overlay on top of the current project. The list
207      * may contain an overlay that represents the current project.
208      * @param overlays
209      */
210     public void setOverlays (List<Overlay> overlays)
211     {
212         _overlays = overlays;
213     }
214     
215     public List<Overlay> getOverlays()
216     {
217         return _overlays;
218     }
219 
220     /* ------------------------------------------------------------ */
221     public void setBaseAppFirst(boolean value)
222     {
223         _baseAppFirst = value;
224     }
225 
226     /* ------------------------------------------------------------ */
227     public boolean getBaseAppFirst()
228     {
229         return _baseAppFirst;
230     }
231     
232     /* ------------------------------------------------------------ */
233     public void setQuickStartWebDescriptor (Resource quickStartWebXml)
234     {
235         _quickStartWebXml = quickStartWebXml;
236     }
237     
238     /* ------------------------------------------------------------ */
239     public Resource getQuickStartWebDescriptor ()
240     {
241         return _quickStartWebXml;
242     }
243     
244     /* ------------------------------------------------------------ */
245     /**
246      * This method is provided as a convenience for jetty maven plugin configuration 
247      * @param resourceBases Array of resources strings to set as a {@link ResourceCollection}. Each resource string may be a comma separated list of resources
248      * @see Resource
249      */
250     public void setResourceBases(String[] resourceBases)
251     {
252         List<String> resources = new ArrayList<String>();
253         for (String rl:resourceBases)
254         {
255             String[] rs = rl.split(" *, *");
256             for (String r:rs)
257                 resources.add(r);
258         }
259         
260         setBaseResource(new ResourceCollection(resources.toArray(new String[resources.size()])));
261     }
262 
263     public List<File> getWebInfLib()
264     {
265         return _webInfJars;
266     }
267     
268     public void setGenerateQuickStart (boolean quickStart)
269     {
270         _isGenerateQuickStart = quickStart;
271     }
272     
273     public boolean isGenerateQuickStart()
274     {
275         return _isGenerateQuickStart;
276     }
277     
278    
279     
280     
281     @Override
282     protected void startWebapp() throws Exception
283     {
284         if (isGenerateQuickStart())
285         {
286             if (getQuickStartWebDescriptor() == null)
287                 throw new IllegalStateException ("No location to generate quickstart descriptor");
288 
289             QuickStartDescriptorGenerator generator = new QuickStartDescriptorGenerator(this, _preconfigProcessor.getXML());
290             try (FileOutputStream fos = new FileOutputStream(getQuickStartWebDescriptor().getFile()))
291             {
292                 generator.generateQuickStartWebXml(fos);
293             }
294         }
295         else
296             super.startWebapp();
297     }
298     
299 
300     @Override
301     public void doStart () throws Exception
302     {
303         //choose if this will be a quickstart or normal start
304         if (!isGenerateQuickStart() && getQuickStartWebDescriptor() != null)
305             setConfigurations(_quickStartConfigurations);
306         else
307         {
308             setConfigurations(_defaultConfigurations);
309             if (isGenerateQuickStart())
310             {
311                 _preconfigProcessor = new PreconfigureDescriptorProcessor();
312                 getMetaData().addDescriptorProcessor(_preconfigProcessor);
313             }
314         }
315         
316 
317         //inject configurations with config from maven plugin    
318         for (Configuration c:getConfigurations())
319         {
320             if (c instanceof EnvConfiguration && getJettyEnvXml() != null)
321                 ((EnvConfiguration)c).setJettyEnvXml(Resource.toURL(new File(getJettyEnvXml())));
322             else if (c instanceof MavenQuickStartConfiguration && getQuickStartWebDescriptor() != null)
323                 ((MavenQuickStartConfiguration)c).setQuickStartWebXml(getQuickStartWebDescriptor());         
324         }
325 
326         //Set up the pattern that tells us where the jars are that need scanning
327 
328         //Allow user to set up pattern for names of jars from the container classpath
329         //that will be scanned - note that by default NO jars are scanned
330         String tmp = _containerIncludeJarPattern;
331         if (tmp==null || "".equals(tmp))
332             tmp = (String)getAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN);           
333         
334         tmp = addPattern(tmp, DEFAULT_CONTAINER_INCLUDE_JAR_PATTERN);
335         setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, tmp);
336         
337         //Allow user to set up pattern of jar names from WEB-INF that will be scanned.
338         //Note that by default ALL jars considered to be in WEB-INF will be scanned - setting
339         //a pattern restricts scanning
340         if (_webInfIncludeJarPattern != null)
341             setAttribute(WebInfConfiguration.WEBINF_JAR_PATTERN, _webInfIncludeJarPattern);
342    
343         //Set up the classes dirs that comprises the equivalent of WEB-INF/classes
344         if (_testClasses != null)
345             _webInfClasses.add(_testClasses);
346         if (_classes != null)
347             _webInfClasses.add(_classes);
348         
349         // Set up the classpath
350         _classpathFiles = new ArrayList<File>();
351         _classpathFiles.addAll(_webInfClasses);
352         _classpathFiles.addAll(_webInfJars);
353 
354         // Initialize map containing all jars in /WEB-INF/lib
355         _webInfJarMap.clear();
356         for (File file : _webInfJars)
357         {
358             // Return all jar files from class path
359             String fileName = file.getName();
360             if (fileName.endsWith(".jar"))
361                 _webInfJarMap.put(fileName, file);
362         }
363         
364         // CHECK setShutdown(false);
365         super.doStart();
366     }
367      
368     public void doStop () throws Exception
369     { 
370         if (_classpathFiles != null)
371             _classpathFiles.clear();
372         _classpathFiles = null;
373         
374         _classes = null;
375         _testClasses = null;
376         
377         if (_webInfJarMap != null)
378             _webInfJarMap.clear();
379        
380         _webInfClasses.clear();
381         _webInfJars.clear();
382         
383         
384         
385         // CHECK setShutdown(true);
386         //just wait a little while to ensure no requests are still being processed
387         Thread.currentThread().sleep(500L);
388         super.doStop();
389         
390         //remove all listeners, servlets and filters. This is because we will re-apply
391         //any context xml file, which means they would potentially be added multiple times.
392         setEventListeners(new EventListener[0]);
393         getServletHandler().setFilters(new FilterHolder[0]);
394         getServletHandler().setFilterMappings(new FilterMapping[0]);
395         getServletHandler().setServlets(new ServletHolder[0]);
396         getServletHandler().setServletMappings(new ServletMapping[0]);
397     }
398 
399     @Override
400     public Resource getResource(String uriInContext) throws MalformedURLException
401     {
402         Resource resource = null;
403         // Try to get regular resource
404         resource = super.getResource(uriInContext);
405 
406         // If no regular resource exists check for access to /WEB-INF/lib or /WEB-INF/classes
407         if ((resource == null || !resource.exists()) && uriInContext != null && _classes != null)
408         {
409             String uri = URIUtil.canonicalPath(uriInContext);
410             if (uri == null)
411                 return null;
412 
413             try
414             {
415                 // Replace /WEB-INF/classes with candidates for the classpath
416                 if (uri.startsWith(WEB_INF_CLASSES_PREFIX))
417                 {
418                     if (uri.equalsIgnoreCase(WEB_INF_CLASSES_PREFIX) || uri.equalsIgnoreCase(WEB_INF_CLASSES_PREFIX+"/"))
419                     {
420                         //exact match for a WEB-INF/classes, so preferentially return the resource matching the web-inf classes
421                         //rather than the test classes
422                         if (_classes != null)
423                             return Resource.newResource(_classes);
424                         else if (_testClasses != null)
425                             return Resource.newResource(_testClasses);
426                     }
427                     else
428                     {
429                         //try matching                       
430                         Resource res = null;
431                         int i=0;
432                         while (res == null && (i < _webInfClasses.size()))
433                         {
434                             String newPath = uri.replace(WEB_INF_CLASSES_PREFIX, _webInfClasses.get(i).getPath());
435                             res = Resource.newResource(newPath);
436                             if (!res.exists())
437                             {
438                                 res = null; 
439                                 i++;
440                             }
441                         }
442                         return res;
443                     }
444                 }       
445                 else if (uri.startsWith(WEB_INF_LIB_PREFIX))
446                 {
447                     // Return the real jar file for all accesses to
448                     // /WEB-INF/lib/*.jar
449                     String jarName = uri.replace(WEB_INF_LIB_PREFIX, "");
450                     if (jarName.startsWith("/") || jarName.startsWith("\\")) 
451                         jarName = jarName.substring(1);
452                     if (jarName.length()==0) 
453                         return null;
454                     File jarFile = _webInfJarMap.get(jarName);
455                     if (jarFile != null)
456                         return Resource.newResource(jarFile.getPath());
457 
458                     return null;
459                 }
460             }
461             catch (MalformedURLException e)
462             {
463                 throw e;
464             }
465             catch (IOException e)
466             {
467                 LOG.ignore(e);
468             }
469         }
470         return resource;
471     }
472 
473     @Override
474     public Set<String> getResourcePaths(String path)
475     {
476         // Try to get regular resource paths - this will get appropriate paths from any overlaid wars etc
477         Set<String> paths = super.getResourcePaths(path);
478         
479         if (path != null)
480         {
481             TreeSet<String> allPaths = new TreeSet<String>();
482             allPaths.addAll(paths);
483             
484             //add in the dependency jars as a virtual WEB-INF/lib entry
485             if (path.startsWith(WEB_INF_LIB_PREFIX))
486             {
487                 for (String fileName : _webInfJarMap.keySet())
488                 {
489                     // Return all jar files from class path
490                     allPaths.add(WEB_INF_LIB_PREFIX + "/" + fileName);
491                 }
492             }
493             else if (path.startsWith(WEB_INF_CLASSES_PREFIX))
494             {
495                 int i=0;
496                
497                 while (i < _webInfClasses.size())
498                 {
499                     String newPath = path.replace(WEB_INF_CLASSES_PREFIX, _webInfClasses.get(i).getPath());
500                     allPaths.addAll(super.getResourcePaths(newPath));
501                     i++;
502                 }
503             }
504             return allPaths;
505         }
506         return paths;
507     }
508     
509     
510     public String addPattern (String s, String pattern)
511     {
512         if (s == null)
513             s = "";
514         else
515             s = s.trim();
516         
517         if (!s.contains(pattern))
518         {
519             if (s.length() != 0)
520                 s = s + "|";
521             s = s + pattern;
522         }
523         
524         return s;
525     }
526 }