View Javadoc

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