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