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