View Javadoc

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