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 
211         // - the base directory
212         str = (String)props.getProperty("base.dir");
213         if (str != null && !"".equals(str.trim()))
214         {
215             webApp.setWar(str);      
216             webApp.setBaseResource(Resource.newResource(str));
217         }
218         
219         // - put virtual webapp base resource first on resource path or not
220         str = (String)props.getProperty("base.first");
221         if (str != null && !"".equals(str.trim()))
222             webApp.setBaseAppFirst(Boolean.getBoolean(str));
223         
224         
225         //For overlays
226         str = (String)props.getProperty("maven.war.includes");
227         List<String> defaultWarIncludes = fromCSV(str);
228         str = (String)props.getProperty("maven.war.excludes");
229         List<String> defaultWarExcludes = fromCSV(str);
230        
231         //List of war artifacts
232         List<Artifact> wars = new ArrayList<Artifact>();
233         
234         //List of OverlayConfigs
235         TreeMap<String, OverlayConfig> orderedConfigs = new TreeMap<String, OverlayConfig>();
236         Enumeration<String> pnames = (Enumeration<String>)props.propertyNames();
237         while (pnames.hasMoreElements())
238         {
239             String n = pnames.nextElement();
240             if (n.startsWith("maven.war.artifact"))
241             {
242                 Artifact a = new Artifact((String)props.get(n));
243                 a.resource = Resource.newResource("jar:"+Resource.toURL(new File(a.path)).toString()+"!/");
244                 wars.add(a);
245             }
246             else if (n.startsWith("maven.war.overlay"))
247             {
248                 OverlayConfig c = new OverlayConfig ((String)props.get(n), defaultWarIncludes, defaultWarExcludes);
249                 orderedConfigs.put(n,c);
250             }
251         }
252         
253     
254         Set<Artifact> matchedWars = new HashSet<Artifact>();
255         
256         //process any overlays and the war type artifacts
257         List<Overlay> overlays = new ArrayList<Overlay>();
258         for (OverlayConfig config:orderedConfigs.values())
259         {
260             //overlays can be individually skipped
261             if (config.isSkip())
262                 continue;
263 
264             //an empty overlay refers to the current project - important for ordering
265             if (config.isCurrentProject())
266             {
267                 Overlay overlay = new Overlay(config, null);
268                 overlays.add(overlay);
269                 continue;
270             }
271 
272             //if a war matches an overlay config
273             Artifact a = getArtifactForOverlayConfig(config, wars);
274             if (a != null)
275             {
276                 matchedWars.add(a);
277                 SelectiveJarResource r = new SelectiveJarResource(new URL("jar:"+Resource.toURL(new File(a.path)).toString()+"!/"));
278                 r.setIncludes(config.getIncludes());
279                 r.setExcludes(config.getExcludes());
280                 Overlay overlay = new Overlay(config, r);
281                 overlays.add(overlay);
282             }
283         }
284 
285         //iterate over the left over war artifacts and unpack them (without include/exclude processing) as necessary
286         for (Artifact a: wars)
287         {
288             if (!matchedWars.contains(a))
289             {
290                 Overlay overlay = new Overlay(null, a.resource);
291                 overlays.add(overlay);
292             }
293         }
294 
295         webApp.setOverlays(overlays);
296      
297 
298         // - the equivalent of web-inf classes
299         str = (String)props.getProperty("classes.dir");
300         if (str != null && !"".equals(str.trim()))
301         {
302             webApp.setClasses(new File(str));
303         }
304         
305         str = (String)props.getProperty("testClasses.dir"); 
306         if (str != null && !"".equals(str.trim()))
307         {
308             webApp.setTestClasses(new File(str));
309         }
310 
311 
312         // - the equivalent of web-inf lib
313         str = (String)props.getProperty("lib.jars");
314         if (str != null && !"".equals(str.trim()))
315         {
316             List<File> jars = new ArrayList<File>();
317             String[] names = str.split(",");
318             for (int j=0; names != null && j < names.length; j++)
319                 jars.add(new File(names[j].trim()));
320             webApp.setWebInfLib(jars);
321         }
322         
323     }
324 
325     /**
326      * @param args
327      * @throws Exception
328      */
329     public void getConfiguration (String[] args)
330     throws Exception
331     {
332         for (int i=0; i<args.length; i++)
333         {
334             //--stop-port
335             if ("--stop-port".equals(args[i]))
336                 stopPort = Integer.parseInt(args[++i]);
337 
338             //--stop-key
339             if ("--stop-key".equals(args[i]))
340                 stopKey = args[++i];
341 
342             //--jettyXml
343             if ("--jetty-xml".equals(args[i]))
344             {
345                 jettyXmls = new ArrayList<File>();
346                 String[] names = args[++i].split(",");
347                 for (int j=0; names!= null && j < names.length; j++)
348                 {
349                     jettyXmls.add(new File(names[j].trim()));
350                 }  
351             }
352 
353             //--context-xml
354             if ("--context-xml".equals(args[i]))
355             {
356                 contextXml = new File(args[++i]);
357             }
358 
359             //--props
360             if ("--props".equals(args[i]))
361             {
362                 File f = new File(args[++i].trim());
363                 props = new Properties();
364                 try (InputStream in = new FileInputStream(f))
365                 {
366                     props.load(in);
367                 }
368             }
369             
370             //--token
371             if ("--token".equals(args[i]))
372             {
373                 token = args[++i].trim();
374             }
375         }
376     }
377 
378 
379     /**
380      * @throws Exception
381      */
382     public void run() throws Exception
383     {
384         LOG.info("Started Jetty Server");
385         server.start();  
386     }
387 
388     
389     /**
390      * @throws Exception
391      */
392     public void join () throws Exception
393     {
394         server.join();
395     }
396     
397     
398     /**
399      * @param e
400      */
401     public void communicateStartupResult (Exception e)
402     {
403         if (token != null)
404         {
405             if (e==null)
406                 System.out.println(token);
407             else
408                 System.out.println(token+"\t"+e.getMessage());
409         }
410     }
411     
412     
413     /**
414      * @throws Exception
415      */
416     public void applyJettyXml() throws Exception
417     {
418         if (jettyXmls == null)
419             return;
420         
421         for ( File xmlFile : jettyXmls )
422         {
423             LOG.info( "Configuring Jetty from xml configuration file = " + xmlFile.getCanonicalPath() );        
424             XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(xmlFile));
425             xmlConfiguration.configure(this.server);
426         }
427     }
428 
429 
430 
431 
432     /**
433      * @param handler
434      * @param handlers
435      */
436     protected void prependHandler (Handler handler, HandlerCollection handlers)
437     {
438         if (handler == null || handlers == null)
439             return;
440 
441         Handler[] existing = handlers.getChildHandlers();
442         Handler[] children = new Handler[existing.length + 1];
443         children[0] = handler;
444         System.arraycopy(existing, 0, children, 1, existing.length);
445         handlers.setHandlers(children);
446     }
447     
448     
449     
450     /**
451      * @param c
452      * @param wars
453      * @return
454      */
455     protected Artifact getArtifactForOverlayConfig (OverlayConfig c, List<Artifact> wars)
456     {
457         if (wars == null || wars.isEmpty() || c == null)
458             return null;
459 
460         Artifact war = null;
461         Iterator<Artifact> itor = wars.iterator();
462         while(itor.hasNext() && war == null)
463         {
464             Artifact a = itor.next();
465             if (c.matchesArtifact(a.gid, a.aid, null))
466                 war = a;
467         }
468         return war;
469     }
470 
471 
472     /**
473      * @param csv
474      * @return
475      */
476     private List<String> fromCSV (String csv)
477     {
478         if (csv == null || "".equals(csv.trim()))
479             return null;
480         String[] atoms = csv.split(",");
481         List<String> list = new ArrayList<String>();
482         for (String a:atoms)
483         {
484             list.add(a.trim());
485         }
486         return list;
487     }
488     
489     
490     
491     /**
492      * @param args
493      */
494     public static final void main(String[] args)
495     {
496         if (args == null)
497            System.exit(1);
498        
499        Starter starter = null;
500        try
501        {
502            starter = new Starter();
503            starter.getConfiguration(args);
504            starter.configureJetty();
505            starter.run();
506            starter.communicateStartupResult(null);
507            starter.join();
508        }
509        catch (Exception e)
510        {
511            starter.communicateStartupResult(e);
512            e.printStackTrace();
513            System.exit(1);
514        }
515 
516     }
517 }