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.FileInputStream;
23  import java.io.InputStream;
24  import java.net.URL;
25  import java.util.ArrayList;
26  import java.util.Enumeration;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Properties;
31  import java.util.Set;
32  import java.util.TreeMap;
33  
34  import org.eclipse.jetty.server.Connector;
35  import org.eclipse.jetty.server.Handler;
36  import org.eclipse.jetty.server.ShutdownMonitor;
37  import org.eclipse.jetty.server.handler.HandlerCollection;
38  import org.eclipse.jetty.util.log.Log;
39  import org.eclipse.jetty.util.log.Logger;
40  import org.eclipse.jetty.util.resource.Resource;
41  import org.eclipse.jetty.util.resource.ResourceCollection;
42  import org.eclipse.jetty.xml.XmlConfiguration;
43  
44  
45  
46  /**
47   * Starter
48   * 
49   * Class which is exec'ed to create a new jetty process. Used by the JettyRunForked mojo.
50   *
51   */
52  public class Starter
53  { 
54      public static final String PORT_SYSPROPERTY = "jetty.port";
55      private static final Logger LOG = Log.getLogger(Starter.class);
56  
57      private List<File> jettyXmls; // list of jetty.xml config files to apply - Mandatory
58      private File contextXml; //name of context xml file to configure the webapp - Mandatory
59  
60      private JettyServer server = new JettyServer();
61      private JettyWebAppContext webApp;
62  
63      
64      private int stopPort=0;
65      private String stopKey=null;
66      private Properties props;
67      private String token;
68  
69      
70      /**
71       * Artifact
72       *
73       * A mock maven Artifact class as the maven jars are not put onto the classpath for the
74       * execution of this class.
75       *
76       */
77      public class Artifact
78      {
79          public String gid;
80          public String aid;
81          public String path;
82          public Resource resource;
83          
84          public Artifact (String csv)
85          {
86              if (csv != null && !"".equals(csv))
87              {
88                  String[] atoms = csv.split(",");
89                  if (atoms.length >= 3)
90                  {
91                      gid = atoms[0].trim();
92                      aid = atoms[1].trim();
93                      path = atoms[2].trim();
94                  }
95              }
96          }
97          
98          public Artifact (String gid, String aid, String path)
99          {
100             this.gid = gid;
101             this.aid = aid;
102             this.path = path;
103         }
104         
105         public boolean equals(Object o)
106         {
107             if (!(o instanceof Artifact))
108                 return false;
109             
110             Artifact ao = (Artifact)o;
111             return (((gid == null && ao.gid == null) || (gid != null && gid.equals(ao.gid)))
112                     &&  ((aid == null && ao.aid == null) || (aid != null && aid.equals(ao.aid))));      
113         }
114     }
115     
116     
117     
118     /**
119      * @throws Exception
120      */
121     public void configureJetty () throws Exception
122     {
123         LOG.debug("Starting Jetty Server ...");
124 
125         //apply any configs from jetty.xml files first 
126         applyJettyXml ();
127 
128         // if the user hasn't configured a connector in the jetty.xml
129         //then use a default
130         Connector[] connectors = this.server.getConnectors();
131         if (connectors == null|| connectors.length == 0)
132         {
133             //if a SystemProperty -Djetty.port=<portnum> has been supplied, use that as the default port
134             MavenServerConnector httpConnector = new MavenServerConnector();
135             httpConnector.setServer(this.server);
136             String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR);
137             httpConnector.setPort(Integer.parseInt(tmp.trim()));
138             connectors = new Connector[] {httpConnector};
139             this.server.setConnectors(connectors);
140         }
141 
142         //check that everything got configured, and if not, make the handlers
143         HandlerCollection handlers = (HandlerCollection) server.getChildHandlerByClass(HandlerCollection.class);
144         if (handlers == null)
145         {
146             handlers = new HandlerCollection();
147             server.setHandler(handlers);
148         }
149 
150         //check if contexts already configured, create if not
151         this.server.configureHandlers();
152 
153         webApp = new JettyWebAppContext();
154         
155         //configure webapp from properties file describing unassembled webapp
156         configureWebApp();
157         
158         //make it a quickstart if the quickstart-web.xml file exists
159         if (webApp.getTempDirectory() != null)
160         {
161             File qs = new File (webApp.getTempDirectory(), "quickstart-web.xml");
162             if (qs.exists() && qs.isFile())
163                 webApp.setQuickStartWebDescriptor(Resource.newResource(qs));
164         }
165         
166         //set up the webapp from the context xml file provided
167         //NOTE: just like jetty:run mojo this means that the context file can
168         //potentially override settings made in the pom. Ideally, we'd like
169         //the pom to override the context xml file, but as the other mojos all
170         //configure a WebAppContext in the pom (the <webApp> element), it is 
171         //already configured by the time the context xml file is applied.
172         if (contextXml != null)
173         {
174             XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(contextXml));
175             xmlConfiguration.getIdMap().put("Server",server);
176             xmlConfiguration.configure(webApp);
177         }
178 
179         this.server.addWebApplication(webApp);
180 
181         if(stopPort>0 && stopKey!=null)
182         {
183             ShutdownMonitor monitor = ShutdownMonitor.getInstance();
184             monitor.setPort(stopPort);
185             monitor.setKey(stopKey);
186             monitor.setExitVm(true);
187         }
188     }
189     
190     
191     /**
192      * @throws Exception
193      */
194     public void configureWebApp ()
195     throws Exception
196     {
197         if (props == null)
198             return;
199         
200         //apply a properties file that defines the things that we configure in the jetty:run plugin:
201         // - the context path
202         String str = (String)props.get("context.path");
203         if (str != null)
204             webApp.setContextPath(str);
205         
206         
207         // - web.xml
208         str = (String)props.get("web.xml");
209         if (str != null)
210             webApp.setDescriptor(str); 
211         
212         str = (String)props.get("quickstart.web.xml");
213         if (str != null)
214             webApp.setQuickStartWebDescriptor(Resource.newResource(new File(str)));
215         
216         // - the tmp directory
217         str = (String)props.getProperty("tmp.dir");
218         if (str != null)
219             webApp.setTempDirectory(new File(str.trim()));
220 
221         str = (String)props.getProperty("tmp.dir.persist");
222         if (str != null)
223             webApp.setPersistTempDirectory(Boolean.valueOf(str));
224 
225         // - the base directories
226         str = (String)props.getProperty("base.dirs");
227         if (str != null && !"".equals(str.trim()))
228         {
229             ResourceCollection bases = new ResourceCollection(str.split(","));
230             webApp.setWar(bases.getResources()[0].toString());
231             webApp.setBaseResource(bases);
232         }
233         
234         // - put virtual webapp base resource first on resource path or not
235         str = (String)props.getProperty("base.first");
236         if (str != null && !"".equals(str.trim()))
237             webApp.setBaseAppFirst(Boolean.valueOf(str));
238         
239         
240         //For overlays
241         str = (String)props.getProperty("maven.war.includes");
242         List<String> defaultWarIncludes = fromCSV(str);
243         str = (String)props.getProperty("maven.war.excludes");
244         List<String> defaultWarExcludes = fromCSV(str);
245        
246         //List of war artifacts
247         List<Artifact> wars = new ArrayList<Artifact>();
248         
249         //List of OverlayConfigs
250         TreeMap<String, OverlayConfig> orderedConfigs = new TreeMap<String, OverlayConfig>();
251         Enumeration<String> pnames = (Enumeration<String>)props.propertyNames();
252         while (pnames.hasMoreElements())
253         {
254             String n = pnames.nextElement();
255             if (n.startsWith("maven.war.artifact"))
256             {
257                 Artifact a = new Artifact((String)props.get(n));
258                 a.resource = Resource.newResource("jar:"+Resource.toURL(new File(a.path)).toString()+"!/");
259                 wars.add(a);
260             }
261             else if (n.startsWith("maven.war.overlay"))
262             {
263                 OverlayConfig c = new OverlayConfig ((String)props.get(n), defaultWarIncludes, defaultWarExcludes);
264                 orderedConfigs.put(n,c);
265             }
266         }
267         
268     
269         Set<Artifact> matchedWars = new HashSet<Artifact>();
270         
271         //process any overlays and the war type artifacts
272         List<Overlay> overlays = new ArrayList<Overlay>();
273         for (OverlayConfig config:orderedConfigs.values())
274         {
275             //overlays can be individually skipped
276             if (config.isSkip())
277                 continue;
278 
279             //an empty overlay refers to the current project - important for ordering
280             if (config.isCurrentProject())
281             {
282                 Overlay overlay = new Overlay(config, null);
283                 overlays.add(overlay);
284                 continue;
285             }
286 
287             //if a war matches an overlay config
288             Artifact a = getArtifactForOverlayConfig(config, wars);
289             if (a != null)
290             {
291                 matchedWars.add(a);
292                 SelectiveJarResource r = new SelectiveJarResource(new URL("jar:"+Resource.toURL(new File(a.path)).toString()+"!/"));
293                 r.setIncludes(config.getIncludes());
294                 r.setExcludes(config.getExcludes());
295                 Overlay overlay = new Overlay(config, r);
296                 overlays.add(overlay);
297             }
298         }
299 
300         //iterate over the left over war artifacts and unpack them (without include/exclude processing) as necessary
301         for (Artifact a: wars)
302         {
303             if (!matchedWars.contains(a))
304             {
305                 Overlay overlay = new Overlay(null, a.resource);
306                 overlays.add(overlay);
307             }
308         }
309 
310         webApp.setOverlays(overlays);
311      
312 
313         // - the equivalent of web-inf classes
314         str = (String)props.getProperty("classes.dir");
315         if (str != null && !"".equals(str.trim()))
316         {
317             webApp.setClasses(new File(str));
318         }
319         
320         str = (String)props.getProperty("testClasses.dir"); 
321         if (str != null && !"".equals(str.trim()))
322         {
323             webApp.setTestClasses(new File(str));
324         }
325 
326 
327         // - the equivalent of web-inf lib
328         str = (String)props.getProperty("lib.jars");
329         if (str != null && !"".equals(str.trim()))
330         {
331             List<File> jars = new ArrayList<File>();
332             String[] names = str.split(",");
333             for (int j=0; names != null && j < names.length; j++)
334                 jars.add(new File(names[j].trim()));
335             webApp.setWebInfLib(jars);
336         }
337         
338     }
339 
340     /**
341      * @param args
342      * @throws Exception
343      */
344     public void getConfiguration (String[] args)
345     throws Exception
346     {
347         for (int i=0; i<args.length; i++)
348         {
349             //--stop-port
350             if ("--stop-port".equals(args[i]))
351                 stopPort = Integer.parseInt(args[++i]);
352 
353             //--stop-key
354             if ("--stop-key".equals(args[i]))
355                 stopKey = args[++i];
356 
357             //--jettyXml
358             if ("--jetty-xml".equals(args[i]))
359             {
360                 jettyXmls = new ArrayList<File>();
361                 String[] names = args[++i].split(",");
362                 for (int j=0; names!= null && j < names.length; j++)
363                 {
364                     jettyXmls.add(new File(names[j].trim()));
365                 }  
366             }
367 
368             //--context-xml
369             if ("--context-xml".equals(args[i]))
370             {
371                 contextXml = new File(args[++i]);
372             }
373 
374             //--props
375             if ("--props".equals(args[i]))
376             {
377                 File f = new File(args[++i].trim());
378                 props = new Properties();
379                 try (InputStream in = new FileInputStream(f))
380                 {
381                     props.load(in);
382                 }
383             }
384             
385             //--token
386             if ("--token".equals(args[i]))
387             {
388                 token = args[++i].trim();
389             }
390         }
391     }
392 
393 
394     /**
395      * @throws Exception
396      */
397     public void run() throws Exception
398     {
399         LOG.info("Started Jetty Server");
400         server.start();  
401     }
402 
403     
404     /**
405      * @throws Exception
406      */
407     public void join () throws Exception
408     {
409         server.join();
410     }
411     
412     
413     /**
414      * @param e
415      */
416     public void communicateStartupResult (Exception e)
417     {
418         if (token != null)
419         {
420             if (e==null)
421                 System.out.println(token);
422             else
423                 System.out.println(token+"\t"+e.getMessage());
424         }
425     }
426     
427     
428     /**
429      * Apply any jetty xml files given
430      * @throws Exception
431      */
432     public void applyJettyXml() throws Exception
433     {
434         if (jettyXmls == null)
435             return;
436         this.server.applyXmlConfigurations(jettyXmls);
437     }
438 
439 
440 
441 
442     /**
443      * @param handler
444      * @param handlers
445      */
446     protected void prependHandler (Handler handler, HandlerCollection handlers)
447     {
448         if (handler == null || handlers == null)
449             return;
450 
451         Handler[] existing = handlers.getChildHandlers();
452         Handler[] children = new Handler[existing.length + 1];
453         children[0] = handler;
454         System.arraycopy(existing, 0, children, 1, existing.length);
455         handlers.setHandlers(children);
456     }
457     
458     
459     
460     /**
461      * @param c
462      * @param wars
463      * @return
464      */
465     protected Artifact getArtifactForOverlayConfig (OverlayConfig c, List<Artifact> wars)
466     {
467         if (wars == null || wars.isEmpty() || c == null)
468             return null;
469 
470         Artifact war = null;
471         Iterator<Artifact> itor = wars.iterator();
472         while(itor.hasNext() && war == null)
473         {
474             Artifact a = itor.next();
475             if (c.matchesArtifact(a.gid, a.aid, null))
476                 war = a;
477         }
478         return war;
479     }
480 
481 
482     /**
483      * @param csv
484      * @return
485      */
486     private List<String> fromCSV (String csv)
487     {
488         if (csv == null || "".equals(csv.trim()))
489             return null;
490         String[] atoms = csv.split(",");
491         List<String> list = new ArrayList<String>();
492         for (String a:atoms)
493         {
494             list.add(a.trim());
495         }
496         return list;
497     }
498     
499     
500     
501     /**
502      * @param args
503      */
504     public static final void main(String[] args)
505     {
506         if (args == null)
507            System.exit(1);
508        
509        Starter starter = null;
510        try
511        {
512            starter = new Starter();
513            starter.getConfiguration(args);
514            starter.configureJetty();
515            starter.run();
516            starter.communicateStartupResult(null);
517            starter.join();
518        }
519        catch (Exception e)
520        {
521            starter.communicateStartupResult(e);
522            e.printStackTrace();
523            System.exit(1);
524        }
525 
526     }
527 }