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