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