1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
47
48
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
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
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
109 for (Module child : module.getChildEdges())
110 {
111 bfsCalculateDepth(child,depth);
112 }
113 }
114
115
116
117
118 public void buildGraph() throws FileNotFoundException, IOException
119 {
120 normalizeDependencies();
121
122
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
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
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
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
284 Pattern pat = Pattern.compile(name);
285 List<Module> matching = new ArrayList<>();
286 do
287 {
288 matching.clear();
289
290
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
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
327 if (sources != null)
328 {
329 module.addSources(sources);
330 via = Main.join(sources, ", ");
331 }
332
333
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
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
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
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;
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
566
567
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
583
584
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
623
624
625
626
627
628 private void updateParentReferencesTo(Module module)
629 {
630 if (module.getName().equals(module.getFilesystemRef()))
631 {
632
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
644 resolvedParents.add(module.getName());
645 }
646 else
647 {
648
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 }