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.net.URL;
24  import java.util.ArrayList;
25  import java.util.Enumeration;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Properties;
30  import java.util.Set;
31  import java.util.TreeMap;
32  
33  import org.eclipse.jetty.server.Connector;
34  import org.eclipse.jetty.server.Handler;
35  import org.eclipse.jetty.server.ShutdownMonitor;
36  import org.eclipse.jetty.server.handler.HandlerCollection;
37  import org.eclipse.jetty.util.log.Log;
38  import org.eclipse.jetty.util.log.Logger;
39  import org.eclipse.jetty.util.resource.Resource;
40  import org.eclipse.jetty.xml.XmlConfiguration;
41  
42  
43  
44  /**
45   * Starter
46   * 
47   * Class which is exec'ed to create a new jetty process. Used by the JettyRunForked mojo.
48   *
49   */
50  public class Starter
51  { 
52      public static final String PORT_SYSPROPERTY = "jetty.port";
53      private static final Logger LOG = Log.getLogger(Starter.class);
54  
55      private List<File> jettyXmls; // list of jetty.xml config files to apply - Mandatory
56      private File contextXml; //name of context xml file to configure the webapp - Mandatory
57  
58      private JettyServer server;
59      private JettyWebAppContext webApp;
60  
61      
62      private int stopPort=0;
63      private String stopKey=null;
64      private Properties props;
65      private String token;
66  
67      
68      /**
69       * Artifact
70       *
71       * A mock maven Artifact class as the maven jars are not put onto the classpath for the
72       * execution of this class.
73       *
74       */
75      public class Artifact
76      {
77          public String gid;
78          public String aid;
79          public String path;
80          public Resource resource;
81          
82          public Artifact (String csv)
83          {
84              if (csv != null && !"".equals(csv))
85              {
86                  String[] atoms = csv.split(",");
87                  if (atoms.length >= 3)
88                  {
89                      gid = atoms[0].trim();
90                      aid = atoms[1].trim();
91                      path = atoms[2].trim();
92                  }
93              }
94          }
95          
96          public Artifact (String gid, String aid, String path)
97          {
98              this.gid = gid;
99              this.aid = aid;
100             this.path = path;
101         }
102         
103         public boolean equals(Object o)
104         {
105             if (!(o instanceof Artifact))
106                 return false;
107             
108             Artifact ao = (Artifact)o;
109             return (((gid == null && ao.gid == null) || (gid != null && gid.equals(ao.gid)))
110                     &&  ((aid == null && ao.aid == null) || (aid != null && aid.equals(ao.aid))));      
111         }
112     }
113     
114     
115     
116     /**
117      * @throws Exception
118      */
119     public void configureJetty () throws Exception
120     {
121         LOG.debug("Starting Jetty Server ...");
122 
123         this.server = JettyServer.getInstance();
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             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                 props.load(new FileInputStream(f));
365             }
366             
367             //--token
368             if ("--token".equals(args[i]))
369             {
370                 token = args[++i].trim();
371             }
372         }
373     }
374 
375 
376     /**
377      * @throws Exception
378      */
379     public void run() throws Exception
380     {
381         LOG.info("Started Jetty Server");
382         server.start();  
383     }
384 
385     
386     /**
387      * @throws Exception
388      */
389     public void join () throws Exception
390     {
391         server.join();
392     }
393     
394     
395     /**
396      * @param e
397      */
398     public void communicateStartupResult (Exception e)
399     {
400         if (token != null)
401         {
402             if (e==null)
403                 System.out.println(token);
404             else
405                 System.out.println(token+"\t"+e.getMessage());
406         }
407     }
408     
409     
410     /**
411      * @throws Exception
412      */
413     public void applyJettyXml() throws Exception
414     {
415         if (jettyXmls == null)
416             return;
417         
418         for ( File xmlFile : jettyXmls )
419         {
420             LOG.info( "Configuring Jetty from xml configuration file = " + xmlFile.getCanonicalPath() );        
421             XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(xmlFile));
422             xmlConfiguration.configure(this.server);
423         }
424     }
425 
426 
427 
428 
429     /**
430      * @param handler
431      * @param handlers
432      */
433     protected void prependHandler (Handler handler, HandlerCollection handlers)
434     {
435         if (handler == null || handlers == null)
436             return;
437 
438         Handler[] existing = handlers.getChildHandlers();
439         Handler[] children = new Handler[existing.length + 1];
440         children[0] = handler;
441         System.arraycopy(existing, 0, children, 1, existing.length);
442         handlers.setHandlers(children);
443     }
444     
445     
446     
447     /**
448      * @param c
449      * @param wars
450      * @return
451      */
452     protected Artifact getArtifactForOverlayConfig (OverlayConfig c, List<Artifact> wars)
453     {
454         if (wars == null || wars.isEmpty() || c == null)
455             return null;
456         
457         Artifact war = null;
458         Iterator<Artifact> itor = wars.iterator();
459         while(itor.hasNext() && war == null)
460         {
461             Artifact a = itor.next();
462             if (((c.getGroupId() == null && a.gid == null) || (c.getGroupId() != null && c.getGroupId().equals(a.gid)))
463             &&  ((c.getArtifactId() == null && a.aid == null) || (c.getArtifactId() != null && c.getArtifactId().equals(a.aid)))
464             &&  ((c.getClassifier() == null) || (c.getClassifier().equals(a.aid))))
465             {
466                 war = a;
467             }
468         }
469         return war;
470     }
471     
472     
473     /**
474      * @param csv
475      * @return
476      */
477     private List<String> fromCSV (String csv)
478     {
479         if (csv == null || "".equals(csv.trim()))
480             return null;
481         String[] atoms = csv.split(",");
482         List<String> list = new ArrayList<String>();
483         for (String a:atoms)
484         {
485             list.add(a.trim());
486         }
487         return list;
488     }
489     
490     
491     
492     /**
493      * @param args
494      */
495     public static final void main(String[] args)
496     {
497         if (args == null)
498            System.exit(1);
499        
500        Starter starter = null;
501        try
502        {
503            starter = new Starter();
504            starter.getConfiguration(args);
505            starter.configureJetty();
506            starter.run();
507            starter.communicateStartupResult(null);
508            starter.join();
509        }
510        catch (Exception e)
511        {
512            starter.communicateStartupResult(e);
513            e.printStackTrace();
514            System.exit(1);
515        }
516 
517     }
518 }