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.FileNotFoundException;
22  import java.io.IOException;
23  import java.nio.file.Path;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  import java.util.Stack;
34  import java.util.regex.Pattern;
35  
36  /**
37   * Access for all modules declared, as well as what is enabled.
38   */
39  public class Modules implements Iterable<Module>
40  {
41      private final BaseHome baseHome;
42      private final StartArgs args;
43      
44      private Map<String, Module> modules = new HashMap<>();
45      /*
46       * modules that may appear in the resolved graph but are undefined in the module system
47       * 
48       * ex: modules/npn/npn-1.7.0_01.mod (property expansion resolves to non-existent file)
49       */
50      private Set<String> missingModules = new HashSet<String>();
51  
52      private int maxDepth = -1;
53      
54      public Modules(BaseHome basehome, StartArgs args)
55      {
56          this.baseHome = basehome;
57          this.args = args;
58      }
59  
60      private Set<String> asNameSet(Set<Module> moduleSet)
61      {
62          Set<String> ret = new HashSet<>();
63          for (Module module : moduleSet)
64          {
65              ret.add(module.getName());
66          }
67          return ret;
68      }
69  
70      private void assertNoCycle(Module module, Stack<String> refs)
71      {
72          for (Module parent : module.getParentEdges())
73          {
74              if (refs.contains(parent.getName()))
75              {
76                  // Cycle detected.
77                  StringBuilder err = new StringBuilder();
78                  err.append("A cyclic reference in the modules has been detected: ");
79                  for (int i = 0; i < refs.size(); i++)
80                  {
81                      if (i > 0)
82                      {
83                          err.append(" -> ");
84                      }
85                      err.append(refs.get(i));
86                  }
87                  err.append(" -> ").append(parent.getName());
88                  throw new IllegalStateException(err.toString());
89              }
90  
91              refs.push(parent.getName());
92              assertNoCycle(parent,refs);
93              refs.pop();
94          }
95      }
96  
97      private void bfsCalculateDepth(final Module module, final int depthNow)
98      {
99          int depth = depthNow + 1;
100 
101         // Set depth on every child first
102         for (Module child : module.getChildEdges())
103         {
104             child.setDepth(Math.max(depth,child.getDepth()));
105             this.maxDepth = Math.max(this.maxDepth,child.getDepth());
106         }
107 
108         // Dive down
109         for (Module child : module.getChildEdges())
110         {
111             bfsCalculateDepth(child,depth);
112         }
113     }
114 
115     /**
116      * Using the provided dependencies, build the module graph
117      */
118     public void buildGraph() throws FileNotFoundException, IOException
119     {
120         normalizeDependencies();
121         
122         // Connect edges
123         for (Module module : modules.values())
124         {
125             for (String parentName : module.getParentNames())
126             {
127                 Module parent = get(parentName);
128 
129                 if (parent == null)
130                 {
131                     if (Props.hasPropertyKey(parentName))
132                     {
133                         StartLog.debug("Module property not expandable (yet) [%s]",parentName);
134                     }
135                     else
136                     {
137                         StartLog.warn("Module not found [%s]",parentName);
138                     }
139                 }
140                 else
141                 {
142                     module.addParentEdge(parent);
143                     parent.addChildEdge(module);
144                 }
145             }
146 
147             for (String optionalParentName : module.getOptionalParentNames())
148             {
149                 Module optional = get(optionalParentName);
150                 if (optional == null)
151                 {
152                     StartLog.debug("Optional module not found [%s]",optionalParentName);
153                 }
154                 else if (optional.isEnabled())
155                 {
156                     module.addParentEdge(optional);
157                     optional.addChildEdge(module);
158                 }
159             }
160         }
161 
162         // Verify there is no cyclic references
163         Stack<String> refs = new Stack<>();
164         for (Module module : modules.values())
165         {
166             refs.push(module.getName());
167             assertNoCycle(module,refs);
168             refs.pop();
169         }
170 
171         // Calculate depth of all modules for sorting later
172         for (Module module : modules.values())
173         {
174             if (module.getParentEdges().isEmpty())
175             {
176                 bfsCalculateDepth(module,0);
177             }
178         }
179     }
180 
181     public void clearMissing()
182     {
183         missingModules.clear();
184     }
185     
186     public Integer count()
187     {
188         return modules.size();
189     }
190 
191     public void dump()
192     {
193         List<Module> ordered = new ArrayList<>();
194         ordered.addAll(modules.values());
195         Collections.sort(ordered,new Module.NameComparator());
196 
197         List<Module> active = resolveEnabled();
198 
199         for (Module module : ordered)
200         {
201             boolean activated = active.contains(module);
202             boolean enabled = module.isEnabled();
203             boolean transitive = activated && !enabled;
204 
205             char status = '-';
206             if (enabled)
207             {
208                 status = '*';
209             }
210             else if (transitive)
211             {
212                 status = '+';
213             }
214 
215             System.out.printf("%n %s Module: %s%n",status,module.getName());
216             if (!module.getName().equals(module.getFilesystemRef()))
217             {
218                 System.out.printf("      Ref: %s%n",module.getFilesystemRef());
219             }
220             for (String parent : module.getParentNames())
221             {
222                 System.out.printf("   Depend: %s%n",parent);
223             }
224             for (String lib : module.getLibs())
225             {
226                 System.out.printf("      LIB: %s%n",lib);
227             }
228             for (String xml : module.getXmls())
229             {
230                 System.out.printf("      XML: %s%n",xml);
231             }
232             if (StartLog.isDebugEnabled())
233             {
234                 System.out.printf("    depth: %d%n",module.getDepth());
235             }
236             if (activated)
237             {
238                 for (String source : module.getSources())
239                 {
240                     System.out.printf("  Enabled: <via> %s%n",source);
241                 }
242                 if (transitive)
243                 {
244                     System.out.printf("  Enabled: <via transitive reference>%n");
245                 }
246             }
247             else
248             {
249                 System.out.printf("  Enabled: <not enabled in this configuration>%n");
250             }
251         }
252     }
253 
254     public void dumpEnabledTree()
255     {
256         List<Module> ordered = new ArrayList<>();
257         ordered.addAll(modules.values());
258         Collections.sort(ordered,new Module.DepthComparator());
259 
260         List<Module> active = resolveEnabled();
261 
262         for (Module module : ordered)
263         {
264             if (active.contains(module))
265             {
266                 // Show module name
267                 String indent = toIndent(module.getDepth());
268                 System.out.printf("%s + Module: %s [%s]%n",indent,module.getName(),module.isEnabled()?"enabled":"transitive");
269             }
270         }
271     }
272 
273     public void enable(String name) throws IOException
274     {
275         List<String> empty = Collections.emptyList();
276         enable(name,empty);
277     }
278     
279     public void enable(String name, List<String> sources) throws IOException
280     {
281         if (name.contains("*"))
282         {
283             // A regex!
284             Pattern pat = Pattern.compile(name);
285             List<Module> matching = new ArrayList<>();
286             do
287             {
288                 matching.clear();
289                 
290                 // find matching entries that are not enabled
291                 for (Map.Entry<String, Module> entry : modules.entrySet())
292                 {
293                     if (pat.matcher(entry.getKey()).matches())
294                     {
295                         if (!entry.getValue().isEnabled())
296                         {
297                             matching.add(entry.getValue());
298                         }
299                     }
300                 }
301                 
302                 // enable them
303                 for (Module module : matching)
304                 {
305                     enableModule(module,sources);
306                 }
307             }
308             while (!matching.isEmpty());
309         }
310         else
311         {
312             Module module = modules.get(name);
313             if (module == null)
314             {
315                 System.err.printf("WARNING: Cannot enable requested module [%s]: not a valid module name.%n",name);
316                 return;
317             }
318             enableModule(module,sources);
319         }
320     }
321 
322     private void enableModule(Module module, List<String> sources) throws IOException
323     {
324         String via = "<transitive>";
325 
326         // Always add the sources
327         if (sources != null)
328         {
329             module.addSources(sources);
330             via = Main.join(sources, ", ");
331         }
332         
333         // If already enabled, nothing else to do
334         if (module.isEnabled())
335         {
336             StartLog.debug("Enabled module: %s (via %s)",module.getName(),via);
337             return;
338         }
339         
340         StartLog.debug("Enabling module: %s (via %s)",module.getName(),via);
341         module.setEnabled(true);
342         args.parseModule(module);
343         module.expandProperties(args.getProperties());
344         
345         // enable any parents that haven't been enabled (yet)
346         Set<String> parentNames = new HashSet<>();
347         parentNames.addAll(module.getParentNames());
348         for(String name: parentNames)
349         {
350             StartLog.debug("Enable parent '%s' of module: %s",name,module.getName());
351             Module parent = modules.get(name);
352             if (parent == null)
353             {
354                 // parent module doesn't exist, yet
355                 Path file = baseHome.getPath("modules/" + name + ".mod");
356                 if (FS.canReadFile(file))
357                 {
358                     parent = registerModule(file);
359                     parent.expandProperties(args.getProperties());
360                     updateParentReferencesTo(parent);
361                 }
362                 else
363                 {
364                     if (!Props.hasPropertyKey(name))
365                     {
366                         StartLog.debug("Missing module definition: [ Mod: %s | File: %s ]",name,file);
367                         missingModules.add(name);
368                     }
369                 }
370             }
371             if (parent != null)
372             {
373                 enableModule(parent,null);
374             }
375         }
376     }
377     
378     private void findChildren(Module module, Set<Module> ret)
379     {
380         ret.add(module);
381         for (Module child : module.getChildEdges())
382         {
383             ret.add(child);
384         }
385     }
386 
387     private void findParents(Module module, Map<String, Module> ret)
388     {
389         ret.put(module.getName(),module);
390         for (Module parent : module.getParentEdges())
391         {
392             ret.put(parent.getName(),parent);
393             findParents(parent,ret);
394         }
395     }
396 
397     public Module get(String name)
398     {
399         return modules.get(name);
400     }
401 
402     public int getMaxDepth()
403     {
404         return maxDepth;
405     }
406 
407     public Set<Module> getModulesAtDepth(int depth)
408     {
409         Set<Module> ret = new HashSet<>();
410         for (Module module : modules.values())
411         {
412             if (module.getDepth() == depth)
413             {
414                 ret.add(module);
415             }
416         }
417         return ret;
418     }
419 
420     @Override
421     public Iterator<Module> iterator()
422     {
423         return modules.values().iterator();
424     }
425 
426     public List<String> normalizeLibs(List<Module> active)
427     {
428         List<String> libs = new ArrayList<>();
429         for (Module module : active)
430         {
431             for (String lib : module.getLibs())
432             {
433                 if (!libs.contains(lib))
434                 {
435                     libs.add(lib);
436                 }
437             }
438         }
439         return libs;
440     }
441 
442     public List<String> normalizeXmls(List<Module> active)
443     {
444         List<String> xmls = new ArrayList<>();
445         for (Module module : active)
446         {
447             for (String xml : module.getXmls())
448             {
449                 if (!xmls.contains(xml))
450                 {
451                     xmls.add(xml);
452                 }
453             }
454         }
455         return xmls;
456     }
457 
458     public Module register(Module module)
459     {
460         modules.put(module.getName(),module);
461         return module;
462     }
463 
464     public void registerParentsIfMissing(Module module) throws IOException
465     {
466         Set<String> parents = new HashSet<>(module.getParentNames());
467         for (String name : parents)
468         {
469             if (!modules.containsKey(name))
470             {
471                 Path file = baseHome.getPath("modules/" + name + ".mod");
472                 if (FS.canReadFile(file))
473                 {
474                     Module parent = registerModule(file);
475                     updateParentReferencesTo(parent);
476                     registerParentsIfMissing(parent);
477                 }
478             }
479         }
480     }
481     
482     public void registerAll() throws IOException
483     {
484         for (Path path : baseHome.getPaths("modules/*.mod"))
485         {
486             registerModule(path);
487         }
488     }
489     
490     // load missing post-expanded dependent modules
491     private void normalizeDependencies() throws FileNotFoundException, IOException
492     {
493         Set<String> expandedModules = new HashSet<>();
494         boolean done = false;
495         while (!done)
496         {
497             done = true;
498             Set<String> missingParents = new HashSet<>();
499 
500             for (Module m : modules.values())
501             {
502                 for (String parent : m.getParentNames())
503                 {
504                     String expanded = args.getProperties().expand(parent);
505                     if (modules.containsKey(expanded) || missingModules.contains(parent) || expandedModules.contains(parent))
506                     {
507                         continue; // found. skip it.
508                     }
509                     done = false;
510                     StartLog.debug("Missing parent module %s == %s for %s",parent,expanded,m);
511                     missingParents.add(parent);
512                 }
513             }
514 
515             for (String missingParent : missingParents)
516             {
517                 String expanded = args.getProperties().expand(missingParent);
518                 Path file = baseHome.getPath("modules/" + expanded + ".mod");
519                 if (FS.canReadFile(file))
520                 {
521                     Module module = registerModule(file);
522                     updateParentReferencesTo(module);
523                     if (!expanded.equals(missingParent))
524                     {
525                         expandedModules.add(missingParent);
526                     }
527                 }
528                 else
529                 {
530                     if (Props.hasPropertyKey(expanded))
531                     {
532                         StartLog.debug("Module property not expandable (yet) [%s]",expanded);
533                         expandedModules.add(missingParent);
534                     }
535                     else
536                     {
537                         StartLog.debug("Missing module definition: %s expanded to %s",missingParent,expanded);
538                         missingModules.add(missingParent);
539                     }
540                 }
541             }
542         }
543     }
544 
545     private Module registerModule(Path file) throws FileNotFoundException, IOException
546     {
547         if (!FS.canReadFile(file))
548         {
549             throw new IOException("Cannot read file: " + file);
550         }
551         StartLog.debug("Registering Module: %s",baseHome.toShortForm(file));
552         Module module = new Module(baseHome,file);
553         return register(module);
554     }
555 
556     public Set<String> resolveChildModulesOf(String moduleName)
557     {
558         Set<Module> ret = new HashSet<>();
559         Module module = get(moduleName);
560         findChildren(module,ret);
561         return asNameSet(ret);
562     }
563 
564     /**
565      * Resolve the execution order of the enabled modules, and all dependant modules, based on depth first transitive reduction.
566      * 
567      * @return the list of active modules (plus dependant modules), in execution order.
568      */
569     public List<Module> resolveEnabled()
570     {
571         Map<String, Module> active = new HashMap<String, Module>();
572 
573         for (Module module : modules.values())
574         {
575             if (module.isEnabled())
576             {
577                 findParents(module,active);
578             }
579         }
580 
581         /*
582          * check against the missing modules
583          * 
584          * Ex: npn should match anything under npn/
585          */
586         for (String missing : missingModules)
587         {
588             for (String activeModule : active.keySet())
589             {
590                 if (missing.startsWith(activeModule))
591                 {
592                     StartLog.warn("** Unable to continue, required dependency missing. [%s]",missing);
593                     StartLog.warn("** As configured, Jetty is unable to start due to a missing enabled module dependency.");
594                     StartLog.warn("** This may be due to a transitive dependency akin to spdy on npn, which resolves based on the JDK in use.");
595                     throw new UsageException(UsageException.ERR_BAD_ARG, "Missing referenced dependency: " + missing);
596                 }
597             }
598         }
599 
600         List<Module> ordered = new ArrayList<>();
601         ordered.addAll(active.values());
602         Collections.sort(ordered,new Module.DepthComparator());
603         return ordered;
604     }
605 
606     public Set<String> resolveParentModulesOf(String moduleName)
607     {
608         Map<String, Module> ret = new HashMap<>();
609         Module module = get(moduleName);
610         findParents(module,ret);
611         return ret.keySet();
612     }
613 
614     private String toIndent(int depth)
615     {
616         char indent[] = new char[depth * 2];
617         Arrays.fill(indent,' ');
618         return new String(indent);
619     }
620 
621     /**
622      * Modules can have a different logical name than to their filesystem reference. This updates existing references to the filesystem form to use the logical
623      * name form.
624      * 
625      * @param module
626      *            the module that might have other modules referring to it.
627      */
628     private void updateParentReferencesTo(Module module)
629     {
630         if (module.getName().equals(module.getFilesystemRef()))
631         {
632             // nothing to do, its sane already
633             return;
634         }
635 
636         for (Module m : modules.values())
637         {
638             Set<String> resolvedParents = new HashSet<>();
639             for (String parent : m.getParentNames())
640             {
641                 if (parent.equals(module.getFilesystemRef()))
642                 {
643                     // use logical name instead
644                     resolvedParents.add(module.getName());
645                 }
646                 else
647                 {
648                     // use name as-is
649                     resolvedParents.add(parent);
650                 }
651             }
652             m.setParentNames(resolvedParents);
653         }
654     }
655 
656     @Override
657     public String toString()
658     {
659         StringBuilder str = new StringBuilder();
660         str.append("Modules[");
661         str.append("count=").append(modules.size());
662         str.append(",<");
663         boolean delim = false;
664         for (String name : modules.keySet())
665         {
666             if (delim)
667             {
668                 str.append(',');
669             }
670             str.append(name);
671             delim = true;
672         }
673         str.append(">");
674         str.append("]");
675         return str.toString();
676     }
677 
678 }