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.start;
20  
21  import java.io.BufferedReader;
22  import java.io.BufferedWriter;
23  import java.io.FileNotFoundException;
24  import java.io.IOException;
25  import java.io.PrintWriter;
26  import java.nio.charset.StandardCharsets;
27  import java.nio.file.Files;
28  import java.nio.file.Path;
29  import java.util.ArrayList;
30  import java.util.HashSet;
31  import java.util.List;
32  import java.util.Locale;
33  import java.util.Set;
34  import java.util.function.Function;
35  import java.util.regex.Matcher;
36  import java.util.regex.Pattern;
37  import java.util.stream.Collectors;
38  
39  import org.eclipse.jetty.start.Props.Prop;
40  import org.eclipse.jetty.start.config.CommandLineConfigSource;
41  
42  /**
43   * Represents a Module metadata, as defined in Jetty.
44   * 
45   * <p>A module consists of:
46   * <ul>
47   * <li>A set of jar files, directories and/or jar file patterns to be added to the classpath</li>
48   * <li>A list of XML configuration files</li>
49   * <li>Properties set either directly or via a file of properties</li>
50   * <li>A set of modules names (or capability names) that this module depends on.</li>
51   * <li>A set of capability names that this module provides (including it's own name).</li>
52   * <li>Licence details for using the module</li>
53   * </ul>
54   * Modules are discovered in the <code>${jetty.home}/modules</code> and 
55   * <code>${jetty.home}/modules</code> directories. A module may refer to 
56   * non-discovered dynamic module in a subdirectory, using a property as part or 
57   * all of the name.
58   * A module may be enabled, either directly by name or transiently via a dependency
59   * from another module by name or provided capability.
60   */
61  public class Module implements Comparable<Module>
62  {
63      private static final String VERSION_UNSPECIFIED = "9.2";
64      static Pattern MOD_NAME = Pattern.compile("^(.*)\\.mod",Pattern.CASE_INSENSITIVE);
65      static Pattern SET_PROPERTY = Pattern.compile("^(#?)\\s*([^=\\s]+)=(.*)$");
66  
67      /** The file of the module */
68      private final Path _path;
69  
70      /** The name of the module */
71      private final String _name;
72      
73      /** Is the module dynamic - ie referenced rather than discovered */
74      private final boolean _dynamic;
75  
76      /** The version of Jetty the module supports */
77      private Version version;
78      
79      /** The module description */
80      private final List<String> _description=new ArrayList<>();
81  
82      /** List of xml configurations for this Module */
83      private final List<String> _xmls=new ArrayList<>();
84      
85      /** List of ini template lines */
86      private final List<String> _iniTemplate=new ArrayList<>();
87      
88      /** List of default config */
89      private final List<String> _defaultConfig=new ArrayList<>();
90      
91      /** List of library options for this Module */
92      private final List<String> _libs=new ArrayList<>();
93      
94      /** List of files for this Module */
95      private final List<String> _files=new ArrayList<>();
96      
97      /** List of selections for this Module */
98      private final Set<String> _enables=new HashSet<>();
99      
100     /** List of provides for this Module */
101     private final Set<String> _provides=new HashSet<>();
102     
103     /** List of tags for this Module */
104     private final List<String> _tags=new ArrayList<>();
105     
106     /** Boolean true if directly enabled, false if all selections are transitive */
107     private boolean _notTransitive;
108     
109     /** Skip File Validation (default: false) */
110     private boolean _skipFilesValidation = false;
111     
112     /** List of jvm Args */
113     private final List<String> _jvmArgs=new ArrayList<>();
114     
115     /** License lines */
116     private final List<String> _license=new ArrayList<>();
117     
118     /** Dependencies */
119     private final Set<String> _depends=new HashSet<>();
120     
121     /** Optional */
122     private final Set<String> _optional=new HashSet<>();
123 
124     public Module(BaseHome basehome, Path path) throws FileNotFoundException, IOException
125     {
126         super();
127         _path = path;
128         
129         // Module name is the / separated path below the modules directory
130         int m=-1;
131         for (int i=path.getNameCount();i-->0;)
132         {
133             if ("modules".equals(path.getName(i).toString()))
134             {
135                 m=i;
136                 break;
137             }
138         }
139         if (m<0)
140             throw new IllegalArgumentException("Module not contained within modules directory: "+basehome.toShortForm(path));
141         String n=path.getName(m+1).toString();
142         for (int i=m+2;i<path.getNameCount();i++)
143             n=n+"/"+path.getName(i).toString();
144         Matcher matcher=MOD_NAME.matcher(n);
145         if (!matcher.matches())
146             throw new IllegalArgumentException("Module filename must have .mod extension: "+basehome.toShortForm(path));
147         _name=matcher.group(1);
148     
149         _provides.add(_name);
150         _dynamic=_name.contains("/");        
151         
152         process(basehome);
153     }
154 
155     public String getName()
156     {
157         return _name;
158     }
159     
160     @Override
161     public boolean equals(Object obj)
162     {
163         if (this == obj)
164         {
165             return true;
166         }
167         if (obj == null)
168         {
169             return false;
170         }
171         if (getClass() != obj.getClass())
172         {
173             return false;
174         }
175         Module other = (Module)obj;
176         if (_path == null)
177             return other._path == null;
178         
179         return _path.equals(other._path);
180     }
181 
182     public void expandProperties(Props props)
183     {
184         Function<String,String> expander = d->{return props.expand(d);};
185         
186         Set<String> tmp=_depends.stream().map(expander).collect(Collectors.toSet());
187         _depends.clear();
188         _depends.addAll(tmp);
189         tmp=_optional.stream().map(expander).collect(Collectors.toSet());
190         _optional.clear();
191         _optional.addAll(tmp);
192     }
193 
194     public List<String> getDefaultConfig()
195     {
196         return _defaultConfig;
197     }
198     
199     public List<String> getIniTemplate()
200     {
201         return _iniTemplate;
202     }
203 
204     public List<String> getFiles()
205     {
206         return _files;
207     }
208 
209     public boolean isSkipFilesValidation()
210     {
211         return _skipFilesValidation;
212     }
213 
214     public List<String> getJvmArgs()
215     {
216         return _jvmArgs;
217     }
218 
219     public List<String> getLibs()
220     {
221         return _libs;
222     }
223 
224     public List<String> getLicense()
225     {
226         return _license;
227     }
228     
229     public List<String> getXmls()
230     {
231         return _xmls;
232     }
233     
234     public Version getVersion()
235     {
236         return version;
237     }
238 
239     public boolean hasDefaultConfig()
240     {
241         return !_defaultConfig.isEmpty();
242     }
243     
244     public boolean hasIniTemplate()
245     {
246         return !_iniTemplate.isEmpty();
247     }
248 
249     @Override
250     public int hashCode()
251     {
252         return _name.hashCode();
253     }
254 
255     public boolean hasLicense()
256     {
257         return (_license != null) && (_license.size() > 0);
258     }
259 
260     /**
261      * Indicates a module that is dynamic in nature
262      * 
263      * @return a module where the name is not in the top level of the modules directory
264      */
265     public boolean isDynamic()
266     {
267         return _dynamic;
268     }
269 
270     public boolean hasFiles(BaseHome baseHome, Props props)
271     {
272         for (String ref : getFiles())
273         {
274             FileArg farg = new FileArg(this,props.expand(ref));
275             Path refPath = baseHome.getBasePath(farg.location);
276             if (!Files.exists(refPath))
277             {
278                 return false;
279             }
280         }
281         return true;
282     }
283 
284     public void process(BaseHome basehome) throws FileNotFoundException, IOException
285     {
286         Pattern section = Pattern.compile("\\s*\\[([^]]*)\\]\\s*");
287 
288         if (!FS.canReadFile(_path))
289         {
290             StartLog.debug("Skipping read of missing file: %s",basehome.toShortForm(_path));
291             return;
292         }
293 
294         try (BufferedReader buf = Files.newBufferedReader(_path,StandardCharsets.UTF_8))
295         {
296             String sectionType = "";
297             String line;
298             while ((line = buf.readLine()) != null)
299             {
300                 line = line.trim();
301 
302                 Matcher sectionMatcher = section.matcher(line);
303 
304                 if (sectionMatcher.matches())
305                 {
306                     sectionType = sectionMatcher.group(1).trim().toUpperCase(Locale.ENGLISH);
307                 }
308                 else
309                 {
310                     // blank lines and comments are valid for ini-template section
311                     if ((line.length() == 0) || line.startsWith("#"))
312                     {
313                         // Remember ini comments and whitespace (empty lines)
314                         // for the [ini-template] section
315                         if ("INI-TEMPLATE".equals(sectionType))
316                         {
317                             _iniTemplate.add(line);
318                         }
319                     }
320                     else
321                     {
322                         switch (sectionType)
323                         {
324                             case "":
325                                 // ignore (this would be entries before first section)
326                                 break;
327                             case "DESCRIPTION":
328                                 _description.add(line);
329                                 break;
330                             case "DEPEND":  
331                             case "DEPENDS":
332                                 _depends.add(line);
333                                 break;
334                             case "FILE":
335                             case "FILES":
336                                 _files.add(line);
337                                 break;
338                             case "TAG":
339                             case "TAGS":
340                                 _tags.add(line);
341                                 break;
342                             case "DEFAULTS": // old name introduced in 9.2.x
343                             case "INI": // new name for 9.3+
344                                 _defaultConfig.add(line);
345                                 break;
346                             case "INI-TEMPLATE":
347                                 _iniTemplate.add(line);
348                                 break;
349                             case "LIB":
350                             case "LIBS":
351                                 _libs.add(line);
352                                 break;
353                             case "LICENSE":
354                             case "LICENSES":
355                             case "LICENCE":
356                             case "LICENCES":
357                                 _license.add(line);
358                                 break;
359                             case "NAME":
360                                 StartLog.warn("Deprecated [name] used in %s",basehome.toShortForm(_path));
361                                 _provides.add(line);
362                                 break;
363                             case "PROVIDE":
364                             case "PROVIDES":
365                                 _provides.add(line);
366                                 break;
367                             case "OPTIONAL":
368                                 _optional.add(line);
369                                 break;
370                             case "EXEC":
371                                 _jvmArgs.add(line);
372                                 break;
373                             case "VERSION":
374                                 if (version != null)
375                                 {
376                                     throw new IOException("[version] already specified");
377                                 }
378                                 version = new Version(line);
379                                 break;
380                             case "XML":
381                                 _xmls.add(line);
382                                 break;
383                             default:
384                                 throw new IOException("Unrecognized module section: [" + sectionType + "]");
385                         }
386                     }
387                 }
388             }
389         }
390         
391         if (version == null)
392         {
393             version = new Version(VERSION_UNSPECIFIED);
394         }
395     }
396 
397     public boolean clearTransitiveEnable()
398     {
399         if (_notTransitive)
400             throw new IllegalStateException("Not Transitive");
401         if (isEnabled())
402         {
403             _enables.clear();
404             return true;
405         }
406         return false;
407     }
408     
409     public void setSkipFilesValidation(boolean skipFilesValidation)
410     {
411         this._skipFilesValidation = skipFilesValidation;
412     }
413     
414     @Override
415     public String toString()
416     {
417         StringBuilder str = new StringBuilder();
418         str.append(getName());
419         char sep='{';
420         if (isDynamic())
421         {
422             str.append(sep).append("dynamic");
423             sep=',';
424         }
425         if (isEnabled())
426         {
427             str.append(sep).append("enabled");
428             sep=',';
429         }
430         if (isTransitive())
431         {
432             str.append(sep).append("transitive");
433             sep=',';
434         }
435         if (sep!='{')
436             str.append('}');
437         return str.toString();
438     }
439 
440     public Set<String> getDepends()
441     {
442         return new HashSet<>(_depends);
443     }
444 
445     public Set<String>  getProvides()
446     {
447         return new HashSet<>(_provides);        
448     }
449     
450     public Set<String> getOptional()
451     {
452         return new HashSet<>(_optional);
453     }
454     
455     public List<String> getDescription()
456     {
457         return _description;
458     }
459     
460     public List<String> getTags()
461     {
462         return _tags;
463     }
464     
465     public String getPrimaryTag()
466     {
467         return _tags.isEmpty()?"*":_tags.get(0);
468     }
469     
470     public boolean isEnabled()
471     {
472         return !_enables.isEmpty();
473     }
474     
475     public Set<String> getEnableSources()
476     {
477         return new HashSet<>(_enables);
478     }
479 
480     /**
481      * @param source String describing where the module was enabled from
482      * @param transitive True if the enable is transitive
483      * @return true if the module was not previously enabled
484      */
485     public boolean enable(String source,boolean transitive)
486     {
487         boolean updated=_enables.isEmpty();
488         if (transitive)
489         {
490             // Ignore transitive selections if explicitly enabled
491             if (!_notTransitive)
492                 _enables.add(source);
493         }
494         else
495         {
496             if (!_notTransitive)
497             {
498                 // Ignore transitive selections if explicitly enabled
499                 updated=true;
500                 _enables.clear(); // clear any transitive enabling
501             }
502             _notTransitive=true;
503             _enables.add(source);
504         }
505         return updated;
506     }
507 
508     public boolean isTransitive()
509     {
510         return isEnabled() && !_notTransitive;
511     }
512     
513     public void writeIniSection(BufferedWriter writer, Props props)
514     {
515         PrintWriter out = new PrintWriter(writer);
516         out.println("# --------------------------------------- ");
517         out.println("# Module: " + getName());
518         for (String line : getDescription())
519             out.append("# ").println(line);
520         out.println("# --------------------------------------- ");
521         out.println("--module=" + getName());
522         out.println();
523         for (String line : getIniTemplate())
524         {
525             Matcher m = SET_PROPERTY.matcher(line);
526             if (m.matches() && m.groupCount()==3)
527             {
528                 String name = m.group(2);
529                 Prop p = props.getProp(name);
530                 if (p!=null && p.origin.startsWith(CommandLineConfigSource.ORIGIN_CMD_LINE))
531                 {
532                     StartLog.info("%-15s property set %s=%s",this._name,name,p.value);
533                     out.printf("%s=%s%n",name,p.value);
534                 }
535                 else
536                     out.println(line);
537             }
538             else
539                 out.println(line);
540         }
541         out.println();
542         out.flush();
543     }
544 
545     @Override
546     public int compareTo(Module m)
547     {
548         int by_tag = getPrimaryTag().compareTo(m.getPrimaryTag());
549         if (by_tag!=0)
550             return by_tag;
551         return getName().compareTo(m.getName());
552     }
553 }