View Javadoc

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