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.File;
22  import java.io.FileNotFoundException;
23  import java.io.IOException;
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                     StartLog.debug("module not found [%s]%n",parentName);
121                 }
122                 else
123                 {
124                     module.addParentEdge(parent);
125                     parent.addChildEdge(module);
126                 }
127             }
128 
129             for (String optionalParentName : module.getOptionalParentNames())
130             {
131                 Module optional = get(optionalParentName);
132                 if (optional == null)
133                 {
134                     StartLog.debug("optional module not found [%s]%n",optionalParentName);
135                 }
136                 else if (optional.isEnabled())
137                 {
138                     module.addParentEdge(optional);
139                     optional.addChildEdge(module);
140                 }
141             }
142         }
143 
144         // Verify there is no cyclic references
145         Stack<String> refs = new Stack<>();
146         for (Module module : modules.values())
147         {
148             refs.push(module.getName());
149             assertNoCycle(module,refs);
150             refs.pop();
151         }
152 
153         // Calculate depth of all modules for sorting later
154         for (Module module : modules.values())
155         {
156             if (module.getParentEdges().isEmpty())
157             {
158                 bfsCalculateDepth(module,0);
159             }
160         }
161     }
162 
163     public Integer count()
164     {
165         return modules.size();
166     }
167 
168     public void dump()
169     {
170         List<Module> ordered = new ArrayList<>();
171         ordered.addAll(modules.values());
172         Collections.sort(ordered,new Module.NameComparator());
173 
174         List<Module> active = resolveEnabled();
175 
176         for (Module module : ordered)
177         {
178             boolean activated = active.contains(module);
179             boolean enabled = module.isEnabled();
180             boolean transitive = activated && !enabled;
181 
182             char status = '-';
183             if (enabled)
184             {
185                 status = '*';
186             }
187             else if (transitive)
188             {
189                 status = '+';
190             }
191 
192             System.out.printf("%n %s Module: %s%n",status,module.getName());
193             if (!module.getName().equals(module.getFilesystemRef()))
194             {
195                 System.out.printf("      Ref: %s%n",module.getFilesystemRef());
196             }
197             for (String parent : module.getParentNames())
198             {
199                 System.out.printf("   Parent: %s%n",parent);
200             }
201             for (String lib : module.getLibs())
202             {
203                 System.out.printf("      LIB: %s%n",lib);
204             }
205             for (String xml : module.getXmls())
206             {
207                 System.out.printf("      XML: %s%n",xml);
208             }
209             if (StartLog.isDebugEnabled())
210             {
211                 System.out.printf("    depth: %d%n",module.getDepth());
212             }
213             if (activated)
214             {
215                 for (String source : module.getSources())
216                 {
217                     System.out.printf("  Enabled: <via> %s%n",source);
218                 }
219                 if (transitive)
220                 {
221                     System.out.printf("  Enabled: <via transitive reference>%n");
222                 }
223             }
224             else
225             {
226                 System.out.printf("  Enabled: <not enabled in this configuration>%n");
227             }
228         }
229     }
230 
231     public void dumpEnabledTree()
232     {
233         List<Module> ordered = new ArrayList<>();
234         ordered.addAll(modules.values());
235         Collections.sort(ordered,new Module.DepthComparator());
236 
237         List<Module> active = resolveEnabled();
238 
239         for (Module module : ordered)
240         {
241             if (active.contains(module))
242             {
243                 // Show module name
244                 String indent = toIndent(module.getDepth());
245                 System.out.printf("%s + Module: %s [%s]%n",indent,module.getName(),module.isEnabled()?"enabled":"transitive");
246             }
247         }
248     }
249 
250     public void enable(String name, List<String> sources)
251     {
252         if (name.contains("*"))
253         {
254             // A regex!
255             Pattern pat = Pattern.compile(name);
256             for (Map.Entry<String, Module> entry : modules.entrySet())
257             {
258                 if (pat.matcher(entry.getKey()).matches())
259                 {
260                     enableModule(entry.getValue(),sources);
261                 }
262             }
263         }
264         else
265         {
266             Module module = modules.get(name);
267             if (module == null)
268             {
269                 System.err.printf("WARNING: Cannot enable requested module [%s]: not a valid module name.%n",name);
270                 return;
271             }
272             enableModule(module,sources);
273         }
274     }
275 
276     private void enableModule(Module module, List<String> sources)
277     {
278         StartLog.debug("Enabling module: %s (via %s)",module.getName(),Main.join(sources,", "));
279         module.setEnabled(true);
280         if (sources != null)
281         {
282             module.addSources(sources);
283         }
284     }
285 
286     private void findChildren(Module module, Set<Module> ret)
287     {
288         ret.add(module);
289         for (Module child : module.getChildEdges())
290         {
291             ret.add(child);
292         }
293     }
294 
295     private void findParents(Module module, Map<String, Module> ret)
296     {
297         ret.put(module.getName(), module);
298         for (Module parent : module.getParentEdges())
299         {
300             ret.put(parent.getName(), parent);
301             findParents(parent,ret);
302         }
303     }
304 
305     public Module get(String name)
306     {
307         return modules.get(name);
308     }
309 
310     public int getMaxDepth()
311     {
312         return maxDepth;
313     }
314 
315     public Set<Module> getModulesAtDepth(int depth)
316     {
317         Set<Module> ret = new HashSet<>();
318         for (Module module : modules.values())
319         {
320             if (module.getDepth() == depth)
321             {
322                 ret.add(module);
323             }
324         }
325         return ret;
326     }
327 
328     @Override
329     public Iterator<Module> iterator()
330     {
331         return modules.values().iterator();
332     }
333 
334     public List<String> normalizeLibs(List<Module> active)
335     {
336         List<String> libs = new ArrayList<>();
337         for (Module module : active)
338         {
339             for (String lib : module.getLibs())
340             {
341                 if (!libs.contains(lib))
342                 {
343                     libs.add(lib);
344                 }
345             }
346         }
347         return libs;
348     }
349 
350     public List<String> normalizeXmls(List<Module> active)
351     {
352         List<String> xmls = new ArrayList<>();
353         for (Module module : active)
354         {
355             for (String xml : module.getXmls())
356             {
357                 if (!xmls.contains(xml))
358                 {
359                     xmls.add(xml);
360                 }
361             }
362         }
363         return xmls;
364     }
365 
366     public Module register(Module module)
367     {
368         modules.put(module.getName(),module);
369         return module;
370     }
371 
372     public void registerAll(BaseHome basehome, StartArgs args) throws IOException
373     {
374         for (File file : basehome.listFiles("modules",new FS.FilenameRegexFilter("^.*\\.mod$")))
375         {
376             registerModule(basehome,args,file);
377         }
378 
379         // load missing post-expanded dependent modules
380         boolean done = false;     
381         while (!done)
382         {
383             done = true;
384             Set<String> missingParents = new HashSet<>();
385 
386             for (Module m : modules.values())
387             {
388                 for (String parent : m.getParentNames())
389                 {
390                     if (modules.containsKey(parent) || missingModules.contains(parent))
391                     {
392                         continue; // found. skip it.
393                     }
394                     done = false;
395                     missingParents.add(parent);
396                 }
397             }
398 
399             for (String missingParent : missingParents)
400             {
401                 File file = basehome.getFile("modules/" + missingParent + ".mod");
402                 if ( FS.canReadFile(file) )
403                 {
404                     Module module = registerModule(basehome,args,file);
405                     updateParentReferencesTo(module);
406                 }
407                 else
408                 {
409                     StartLog.debug("Missing module definition: [ Mod: %s | File: %s]", missingParent, file);
410                     missingModules.add(missingParent);
411                 }
412             }
413         }
414     }
415 
416     private Module registerModule(BaseHome basehome, StartArgs args, File file) throws FileNotFoundException, IOException
417     {
418         if (!FS.canReadFile(file))
419         {
420             throw new IOException("Cannot read file: " + file);
421         }
422         StartLog.debug("Registering Module: %s",basehome.toShortForm(file));
423         Module module = new Module(basehome,file);
424         module.expandProperties(args.getProperties());
425         return register(module);
426     }
427 
428     public Set<String> resolveChildModulesOf(String moduleName)
429     {
430         Set<Module> ret = new HashSet<>();
431         Module module = get(moduleName);
432         findChildren(module,ret);
433         return asNameSet(ret);
434     }
435 
436     /**
437      * Resolve the execution order of the enabled modules, and all dependant modules, based on depth first transitive reduction.
438      * 
439      * @return the list of active modules (plus dependant modules), in execution order.
440      */
441     public List<Module> resolveEnabled()
442     {
443         Map<String, Module> active = new HashMap<String,Module>();
444 
445         for (Module module : modules.values())
446         {
447             if (module.isEnabled())
448             {
449                 findParents(module,active);
450             }
451         }
452 
453         /*
454          * check against the missing modules
455          * 
456          * Ex: npn should match anything under npn/
457          */
458         for ( String missing : missingModules )
459         {
460             for (String activeModule: active.keySet())
461             {                
462                 if ( missing.startsWith(activeModule) )
463                 {
464                     StartLog.warn("** Unable to continue, required dependency missing. [%s]", missing);
465                     StartLog.warn("** As configured, Jetty is unable to start due to a missing enabled module dependency.");
466                     StartLog.warn("** This may be due to a transitive dependency akin to spdy on npn, which resolves based on the JDK in use.");
467                     return Collections.emptyList();
468                 }
469             }
470         }
471         
472         List<Module> ordered = new ArrayList<>();
473         ordered.addAll(active.values());
474         Collections.sort(ordered,new Module.DepthComparator());
475         return ordered;
476     }
477 
478     public Set<String> resolveParentModulesOf(String moduleName)
479     {
480         Map<String,Module> ret = new HashMap<>();
481         Module module = get(moduleName);
482         findParents(module,ret);
483         return ret.keySet();
484     }
485 
486     private String toIndent(int depth)
487     {
488         char indent[] = new char[depth * 2];
489         Arrays.fill(indent,' ');
490         return new String(indent);
491     }
492 
493     /**
494      * Modules can have a different logical name than to their filesystem reference. This updates existing references to the filesystem form to use the logical
495      * name form.
496      * 
497      * @param module
498      *            the module that might have other modules referring to it.
499      */
500     private void updateParentReferencesTo(Module module)
501     {
502         if (module.getName().equals(module.getFilesystemRef()))
503         {
504             // nothing to do, its sane already
505             return;
506         }
507 
508         for (Module m : modules.values())
509         {
510             Set<String> resolvedParents = new HashSet<>();
511             for (String parent : m.getParentNames())
512             {
513                 if (parent.equals(module.getFilesystemRef()))
514                 {
515                     // use logical name instead
516                     resolvedParents.add(module.getName());
517                 }
518                 else
519                 {
520                     // use name as-is
521                     resolvedParents.add(parent);
522                 }
523             }
524             m.setParentNames(resolvedParents);
525         }
526     }
527 }