View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 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.FileNotFoundException;
23  import java.io.IOException;
24  import java.io.InputStreamReader;
25  import java.nio.charset.StandardCharsets;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.text.CollationKey;
29  import java.text.Collator;
30  import java.util.ArrayList;
31  import java.util.Collections;
32  import java.util.Comparator;
33  import java.util.HashSet;
34  import java.util.List;
35  import java.util.Locale;
36  import java.util.Set;
37  import java.util.regex.Matcher;
38  import java.util.regex.Pattern;
39  
40  /**
41   * Represents a Module metadata, as defined in Jetty.
42   */
43  public class Module
44  {
45      public static class DepthComparator implements Comparator<Module>
46      {
47          private Collator collator = Collator.getInstance();
48  
49          @Override
50          public int compare(Module o1, Module o2)
51          {
52              // order by depth first.
53              int diff = o1.depth - o2.depth;
54              if (diff != 0)
55              {
56                  return diff;
57              }
58              // then by name (not really needed, but makes for predictable test cases)
59              CollationKey k1 = collator.getCollationKey(o1.fileRef);
60              CollationKey k2 = collator.getCollationKey(o2.fileRef);
61              return k1.compareTo(k2);
62          }
63      }
64  
65      public static class NameComparator implements Comparator<Module>
66      {
67          private Collator collator = Collator.getInstance();
68  
69          @Override
70          public int compare(Module o1, Module o2)
71          {
72              // by name (not really needed, but makes for predictable test cases)
73              CollationKey k1 = collator.getCollationKey(o1.fileRef);
74              CollationKey k2 = collator.getCollationKey(o2.fileRef);
75              return k1.compareTo(k2);
76          }
77      }
78  
79      /** The file of the module */
80      private Path file;
81      /** The name of this Module (as a filesystem reference) */
82      private String fileRef;
83      /**
84       * The logical name of this module (for property selected references), And to aid in duplicate detection.
85       */
86      private String logicalName;
87      /** The depth of the module in the tree */
88      private int depth = 0;
89      /** Set of Modules, by name, that this Module depends on */
90      private Set<String> parentNames;
91      /** Set of Modules, by name, that this Module optionally depend on */
92      private Set<String> optionalParentNames;
93      /** The Edges to parent modules */
94      private Set<Module> parentEdges;
95      /** The Edges to child modules */
96      private Set<Module> childEdges;
97      /** List of xml configurations for this Module */
98      private List<String> xmls;
99      /** List of ini template lines */
100     private List<String> defaultConfig;
101     private boolean hasDefaultConfig = false;
102     /** List of library options for this Module */
103     private List<String> libs;
104     /** List of files for this Module */
105     private List<String> files;
106     /** List of jvm Args */
107     private List<String> jvmArgs;
108     /** License lines */
109     private List<String> license;
110 
111     /** Is this Module enabled via start.jar command line, start.ini, or start.d/*.ini ? */
112     private boolean enabled = false;
113     /** List of sources that enabled this module */
114     private final Set<String> sources = new HashSet<>();
115     private boolean licenseAck = false;
116 
117     public Module(BaseHome basehome, Path file) throws FileNotFoundException, IOException
118     {
119         this.file = file;
120 
121         // Strip .mod
122         this.fileRef = Pattern.compile(".mod$",Pattern.CASE_INSENSITIVE).matcher(file.getFileName().toString()).replaceFirst("");
123         this.logicalName = fileRef;
124 
125         init(basehome);
126         process(basehome);
127     }
128 
129     public void addChildEdge(Module child)
130     {
131         if (childEdges.contains(child))
132         {
133             // already present, skip
134             return;
135         }
136         this.childEdges.add(child);
137     }
138 
139     public void addParentEdge(Module parent)
140     {
141         if (parentEdges.contains(parent))
142         {
143             // already present, skip
144             return;
145         }
146         this.parentEdges.add(parent);
147     }
148 
149     public void addSources(List<String> sources)
150     {
151         this.sources.addAll(sources);
152     }
153 
154     public void clearSources()
155     {
156         this.sources.clear();
157     }
158 
159     @Override
160     public boolean equals(Object obj)
161     {
162         if (this == obj)
163         {
164             return true;
165         }
166         if (obj == null)
167         {
168             return false;
169         }
170         if (getClass() != obj.getClass())
171         {
172             return false;
173         }
174         Module other = (Module)obj;
175         if (fileRef == null)
176         {
177             if (other.fileRef != null)
178             {
179                 return false;
180             }
181         }
182         else if (!fileRef.equals(other.fileRef))
183         {
184             return false;
185         }
186         return true;
187     }
188 
189     public void expandProperties(Props props)
190     {
191         // Expand Parents
192         Set<String> parents = new HashSet<>();
193         for (String parent : parentNames)
194         {
195             parents.add(props.expand(parent));
196         }
197         parentNames.clear();
198         parentNames.addAll(parents);
199     }
200 
201     public Set<Module> getChildEdges()
202     {
203         return childEdges;
204     }
205 
206     public int getDepth()
207     {
208         return depth;
209     }
210 
211     public List<String> getFiles()
212     {
213         return files;
214     }
215 
216     public String getFilesystemRef()
217     {
218         return fileRef;
219     }
220 
221     public List<String> getDefaultConfig()
222     {
223         return defaultConfig;
224     }
225 
226     public boolean hasDefaultConfig()
227     {
228         return hasDefaultConfig;
229     }
230 
231     public List<String> getLibs()
232     {
233         return libs;
234     }
235 
236     public String getName()
237     {
238         return logicalName;
239     }
240 
241     public Set<String> getOptionalParentNames()
242     {
243         return optionalParentNames;
244     }
245 
246     public Set<Module> getParentEdges()
247     {
248         return parentEdges;
249     }
250 
251     public Set<String> getParentNames()
252     {
253         return parentNames;
254     }
255 
256     public Set<String> getSources()
257     {
258         return Collections.unmodifiableSet(sources);
259     }
260 
261     public List<String> getXmls()
262     {
263         return xmls;
264     }
265 
266     public List<String> getJvmArgs()
267     {
268         return jvmArgs;
269     }
270 
271     public boolean hasLicense()
272     {
273         return license != null && license.size() > 0;
274     }
275 
276     public boolean acknowledgeLicense() throws IOException
277     {
278         if (!hasLicense() || licenseAck)
279         {
280             return true;
281         }
282 
283         System.err.printf("%nModule %s:%n",getName());
284         System.err.printf(" + contains software not provided by the Eclipse Foundation!%n");
285         System.err.printf(" + contains software not covered by the Eclipse Public License!%n");
286         System.err.printf(" + has not been audited for compliance with its license%n");
287         System.err.printf("%n");
288         for (String l : getLicense())
289         {
290             System.err.printf("    %s%n",l);
291         }
292 
293         String propBasedAckName = "org.eclipse.jetty.start.ack.license." + getName();
294         String propBasedAckValue = System.getProperty(propBasedAckName);
295         if (propBasedAckValue != null)
296         {
297             StartLog.log("TESTING MODE", "Programmatic ACK - %s=%s",propBasedAckName,propBasedAckValue);
298             licenseAck = Boolean.parseBoolean(propBasedAckValue);
299         }
300         else
301         {
302             if (Boolean.getBoolean("org.eclipse.jetty.start.testing"))
303             {
304                 throw new RuntimeException("Test Configuration Missing - Pre-specify answer to (" + propBasedAckName + ") in test case");
305             }
306 
307             try (BufferedReader input = new BufferedReader(new InputStreamReader(System.in)))
308             {
309                 System.err.printf("%nProceed (y/N)? ");
310                 String line = input.readLine();
311 
312                 licenseAck = !(line == null || line.length() == 0 || !line.toLowerCase().startsWith("y"));
313             }
314         }
315 
316         return licenseAck;
317     }
318 
319     public List<String> getLicense()
320     {
321         return license;
322     }
323 
324     @Override
325     public int hashCode()
326     {
327         final int prime = 31;
328         int result = 1;
329         result = (prime * result) + ((fileRef == null)?0:fileRef.hashCode());
330         return result;
331     }
332 
333     private void init(BaseHome basehome)
334     {
335         parentNames = new HashSet<>();
336         optionalParentNames = new HashSet<>();
337         parentEdges = new HashSet<>();
338         childEdges = new HashSet<>();
339         xmls = new ArrayList<>();
340         defaultConfig = new ArrayList<>();
341         libs = new ArrayList<>();
342         files = new ArrayList<>();
343         jvmArgs = new ArrayList<>();
344         license = new ArrayList<>();
345 
346         String name = basehome.toShortForm(file);
347 
348         // Find module system name (usually in the form of a filesystem reference)
349         Pattern pat = Pattern.compile("^.*[/\\\\]{1}modules[/\\\\]{1}(.*).mod$",Pattern.CASE_INSENSITIVE);
350         Matcher mat = pat.matcher(name);
351         if (!mat.find())
352         {
353             throw new RuntimeException("Invalid Module location (must be located under /modules/ directory): " + name);
354         }
355         this.fileRef = mat.group(1).replace('\\','/');
356         this.logicalName = this.fileRef;
357     }
358 
359     public boolean isEnabled()
360     {
361         return enabled;
362     }
363 
364     public void process(BaseHome basehome) throws FileNotFoundException, IOException
365     {
366         Pattern section = Pattern.compile("\\s*\\[([^]]*)\\]\\s*");
367 
368         if (!FS.canReadFile(file))
369         {
370             StartLog.debug("Skipping read of missing file: %s",basehome.toShortForm(file));
371             return;
372         }
373 
374         try (BufferedReader buf = Files.newBufferedReader(file,StandardCharsets.UTF_8))
375         {
376             String sectionType = "";
377             String line;
378             while ((line = buf.readLine()) != null)
379             {
380                 line = line.trim();
381 
382                 Matcher sectionMatcher = section.matcher(line);
383 
384                 if (sectionMatcher.matches())
385                 {
386                     sectionType = sectionMatcher.group(1).trim().toUpperCase(Locale.ENGLISH);
387                 }
388                 else
389                 {
390                     // blank lines and comments are valid for ini-template section
391                     if ((line.length() == 0) || line.startsWith("#"))
392                     {
393                         if ("INI-TEMPLATE".equals(sectionType))
394                         {
395                             defaultConfig.add(line);
396                         }
397                     }
398                     else
399                     {
400                         switch (sectionType)
401                         {
402                             case "":
403                                 // ignore (this would be entries before first section)
404                                 break;
405                             case "DEPEND":
406                                 parentNames.add(line);
407                                 break;
408                             case "FILES":
409                                 files.add(line);
410                                 break;
411                             case "DEFAULTS":
412                             case "INI-TEMPLATE":
413                                 defaultConfig.add(line);
414                                 hasDefaultConfig = true;
415                                 break;
416                             case "LIB":
417                                 libs.add(line);
418                                 break;
419                             case "LICENSE":
420                             case "LICENCE":
421                                 license.add(line);
422                                 break;
423                             case "NAME":
424                                 logicalName = line;
425                                 break;
426                             case "OPTIONAL":
427                                 optionalParentNames.add(line);
428                                 break;
429                             case "EXEC":
430                                 jvmArgs.add(line);
431                                 break;
432                             case "XML":
433                                 xmls.add(line);
434                                 break;
435                             default:
436                                 throw new IOException("Unrecognized Module section: [" + sectionType + "]");
437                         }
438                     }
439                 }
440             }
441         }
442     }
443 
444     public void setDepth(int depth)
445     {
446         this.depth = depth;
447     }
448 
449     public void setEnabled(boolean enabled)
450     {
451         this.enabled = enabled;
452     }
453 
454     public void setParentNames(Set<String> parents)
455     {
456         this.parentNames.clear();
457         this.parentEdges.clear();
458         if (parents != null)
459         {
460             this.parentNames.addAll(parents);
461         }
462     }
463 
464     @Override
465     public String toString()
466     {
467         StringBuilder str = new StringBuilder();
468         str.append("Module[").append(logicalName);
469         if (!logicalName.equals(fileRef))
470         {
471             str.append(",file=").append(fileRef);
472         }
473         if (enabled)
474         {
475             str.append(",enabled");
476         }
477         str.append(']');
478         return str.toString();
479     }
480 
481 }