| // |
| // ======================================================================== |
| // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.start; |
| |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.nio.file.Path; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Stack; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Access for all modules declared, as well as what is enabled. |
| */ |
| public class Modules implements Iterable<Module> |
| { |
| private final BaseHome baseHome; |
| private final StartArgs args; |
| |
| private Map<String, Module> modules = new HashMap<>(); |
| /* |
| * modules that may appear in the resolved graph but are undefined in the module system |
| * |
| * ex: modules/npn/npn-1.7.0_01.mod (property expansion resolves to non-existent file) |
| */ |
| private Set<String> missingModules = new HashSet<String>(); |
| |
| private int maxDepth = -1; |
| |
| public Modules(BaseHome basehome, StartArgs args) |
| { |
| this.baseHome = basehome; |
| this.args = args; |
| } |
| |
| private Set<String> asNameSet(Set<Module> moduleSet) |
| { |
| Set<String> ret = new HashSet<>(); |
| for (Module module : moduleSet) |
| { |
| ret.add(module.getName()); |
| } |
| return ret; |
| } |
| |
| private void assertNoCycle(Module module, Stack<String> refs) |
| { |
| for (Module parent : module.getParentEdges()) |
| { |
| if (refs.contains(parent.getName())) |
| { |
| // Cycle detected. |
| StringBuilder err = new StringBuilder(); |
| err.append("A cyclic reference in the modules has been detected: "); |
| for (int i = 0; i < refs.size(); i++) |
| { |
| if (i > 0) |
| { |
| err.append(" -> "); |
| } |
| err.append(refs.get(i)); |
| } |
| err.append(" -> ").append(parent.getName()); |
| throw new IllegalStateException(err.toString()); |
| } |
| |
| refs.push(parent.getName()); |
| assertNoCycle(parent,refs); |
| refs.pop(); |
| } |
| } |
| |
| private void bfsCalculateDepth(final Module module, final int depthNow) |
| { |
| int depth = depthNow + 1; |
| |
| // Set depth on every child first |
| for (Module child : module.getChildEdges()) |
| { |
| child.setDepth(Math.max(depth,child.getDepth())); |
| this.maxDepth = Math.max(this.maxDepth,child.getDepth()); |
| } |
| |
| // Dive down |
| for (Module child : module.getChildEdges()) |
| { |
| bfsCalculateDepth(child,depth); |
| } |
| } |
| |
| /** |
| * Using the provided dependencies, build the module graph |
| */ |
| public void buildGraph() throws FileNotFoundException, IOException |
| { |
| normalizeDependencies(); |
| |
| // Connect edges |
| for (Module module : modules.values()) |
| { |
| for (String parentName : module.getParentNames()) |
| { |
| Module parent = get(parentName); |
| |
| if (parent == null) |
| { |
| if (parentName.contains("${")) |
| { |
| StartLog.debug("module not found [%s]%n",parentName); |
| } |
| else |
| { |
| StartLog.warn("module not found [%s]%n",parentName); |
| } |
| } |
| else |
| { |
| module.addParentEdge(parent); |
| parent.addChildEdge(module); |
| } |
| } |
| |
| for (String optionalParentName : module.getOptionalParentNames()) |
| { |
| Module optional = get(optionalParentName); |
| if (optional == null) |
| { |
| StartLog.debug("optional module not found [%s]%n",optionalParentName); |
| } |
| else if (optional.isEnabled()) |
| { |
| module.addParentEdge(optional); |
| optional.addChildEdge(module); |
| } |
| } |
| } |
| |
| // Verify there is no cyclic references |
| Stack<String> refs = new Stack<>(); |
| for (Module module : modules.values()) |
| { |
| refs.push(module.getName()); |
| assertNoCycle(module,refs); |
| refs.pop(); |
| } |
| |
| // Calculate depth of all modules for sorting later |
| for (Module module : modules.values()) |
| { |
| if (module.getParentEdges().isEmpty()) |
| { |
| bfsCalculateDepth(module,0); |
| } |
| } |
| } |
| |
| public Integer count() |
| { |
| return modules.size(); |
| } |
| |
| public void dump() |
| { |
| List<Module> ordered = new ArrayList<>(); |
| ordered.addAll(modules.values()); |
| Collections.sort(ordered,new Module.NameComparator()); |
| |
| List<Module> active = resolveEnabled(); |
| |
| for (Module module : ordered) |
| { |
| boolean activated = active.contains(module); |
| boolean enabled = module.isEnabled(); |
| boolean transitive = activated && !enabled; |
| |
| char status = '-'; |
| if (enabled) |
| { |
| status = '*'; |
| } |
| else if (transitive) |
| { |
| status = '+'; |
| } |
| |
| System.out.printf("%n %s Module: %s%n",status,module.getName()); |
| if (!module.getName().equals(module.getFilesystemRef())) |
| { |
| System.out.printf(" Ref: %s%n",module.getFilesystemRef()); |
| } |
| for (String parent : module.getParentNames()) |
| { |
| System.out.printf(" Depend: %s%n",parent); |
| } |
| for (String lib : module.getLibs()) |
| { |
| System.out.printf(" LIB: %s%n",lib); |
| } |
| for (String xml : module.getXmls()) |
| { |
| System.out.printf(" XML: %s%n",xml); |
| } |
| if (StartLog.isDebugEnabled()) |
| { |
| System.out.printf(" depth: %d%n",module.getDepth()); |
| } |
| if (activated) |
| { |
| for (String source : module.getSources()) |
| { |
| System.out.printf(" Enabled: <via> %s%n",source); |
| } |
| if (transitive) |
| { |
| System.out.printf(" Enabled: <via transitive reference>%n"); |
| } |
| } |
| else |
| { |
| System.out.printf(" Enabled: <not enabled in this configuration>%n"); |
| } |
| } |
| } |
| |
| public void dumpEnabledTree() |
| { |
| List<Module> ordered = new ArrayList<>(); |
| ordered.addAll(modules.values()); |
| Collections.sort(ordered,new Module.DepthComparator()); |
| |
| List<Module> active = resolveEnabled(); |
| |
| for (Module module : ordered) |
| { |
| if (active.contains(module)) |
| { |
| // Show module name |
| String indent = toIndent(module.getDepth()); |
| System.out.printf("%s + Module: %s [%s]%n",indent,module.getName(),module.isEnabled()?"enabled":"transitive"); |
| } |
| } |
| } |
| |
| public void enable(String name, List<String> sources) throws IOException |
| { |
| if (name.contains("*")) |
| { |
| // A regex! |
| Pattern pat = Pattern.compile(name); |
| List<Module> matching = new ArrayList<>(); |
| do |
| { |
| matching.clear(); |
| |
| // find matching entries that are not enabled |
| for (Map.Entry<String, Module> entry : modules.entrySet()) |
| { |
| if (pat.matcher(entry.getKey()).matches()) |
| { |
| if (!entry.getValue().isEnabled()) |
| { |
| matching.add(entry.getValue()); |
| } |
| } |
| } |
| |
| // enable them |
| for (Module module : matching) |
| { |
| enableModule(module,sources); |
| } |
| } |
| while (!matching.isEmpty()); |
| } |
| else |
| { |
| Module module = modules.get(name); |
| if (module == null) |
| { |
| System.err.printf("WARNING: Cannot enable requested module [%s]: not a valid module name.%n",name); |
| return; |
| } |
| enableModule(module,sources); |
| } |
| } |
| |
| private void enableModule(Module module, List<String> sources) throws IOException |
| { |
| if (module.isEnabled()) |
| { |
| // already enabled, skip |
| return; |
| } |
| |
| StartLog.debug("Enabling module: %s (via %s)",module.getName(),Main.join(sources,", ")); |
| module.setEnabled(true); |
| args.parseModule(module); |
| module.expandProperties(args.getProperties()); |
| if (sources != null) |
| { |
| module.addSources(sources); |
| } |
| |
| // enable any parents that haven't been enabled (yet) |
| Set<String> parentNames = new HashSet<>(); |
| parentNames.addAll(module.getParentNames()); |
| for(String name: parentNames) |
| { |
| StartLog.debug("Enable parent '%s' of module: %s",name,module.getName()); |
| Module parent = modules.get(name); |
| if (parent == null) |
| { |
| // parent module doesn't exist, yet |
| Path file = baseHome.getPath("modules/" + name + ".mod"); |
| if (FS.canReadFile(file)) |
| { |
| parent = registerModule(file); |
| updateParentReferencesTo(parent); |
| } |
| else |
| { |
| StartLog.debug("Missing module definition: [ Mod: %s | File: %s ]",name,file); |
| missingModules.add(name); |
| } |
| } |
| if (parent != null) |
| { |
| enableModule(parent,sources); |
| } |
| } |
| } |
| |
| private void findChildren(Module module, Set<Module> ret) |
| { |
| ret.add(module); |
| for (Module child : module.getChildEdges()) |
| { |
| ret.add(child); |
| } |
| } |
| |
| private void findParents(Module module, Map<String, Module> ret) |
| { |
| ret.put(module.getName(),module); |
| for (Module parent : module.getParentEdges()) |
| { |
| ret.put(parent.getName(),parent); |
| findParents(parent,ret); |
| } |
| } |
| |
| public Module get(String name) |
| { |
| return modules.get(name); |
| } |
| |
| public int getMaxDepth() |
| { |
| return maxDepth; |
| } |
| |
| public Set<Module> getModulesAtDepth(int depth) |
| { |
| Set<Module> ret = new HashSet<>(); |
| for (Module module : modules.values()) |
| { |
| if (module.getDepth() == depth) |
| { |
| ret.add(module); |
| } |
| } |
| return ret; |
| } |
| |
| @Override |
| public Iterator<Module> iterator() |
| { |
| return modules.values().iterator(); |
| } |
| |
| public List<String> normalizeLibs(List<Module> active) |
| { |
| List<String> libs = new ArrayList<>(); |
| for (Module module : active) |
| { |
| for (String lib : module.getLibs()) |
| { |
| if (!libs.contains(lib)) |
| { |
| libs.add(lib); |
| } |
| } |
| } |
| return libs; |
| } |
| |
| public List<String> normalizeXmls(List<Module> active) |
| { |
| List<String> xmls = new ArrayList<>(); |
| for (Module module : active) |
| { |
| for (String xml : module.getXmls()) |
| { |
| if (!xmls.contains(xml)) |
| { |
| xmls.add(xml); |
| } |
| } |
| } |
| return xmls; |
| } |
| |
| public Module register(Module module) |
| { |
| modules.put(module.getName(),module); |
| return module; |
| } |
| |
| public void registerParentsIfMissing(Module module) throws IOException |
| { |
| Set<String> parents = new HashSet<>(module.getParentNames()); |
| for (String name : parents) |
| { |
| if (!modules.containsKey(name)) |
| { |
| Path file = baseHome.getPath("modules/" + name + ".mod"); |
| if (FS.canReadFile(file)) |
| { |
| Module parent = registerModule(file); |
| updateParentReferencesTo(parent); |
| registerParentsIfMissing(parent); |
| } |
| } |
| } |
| } |
| |
| public void registerAll() throws IOException |
| { |
| for (Path path : baseHome.getPaths("modules/*.mod")) |
| { |
| registerModule(path); |
| } |
| } |
| |
| // load missing post-expanded dependent modules |
| private void normalizeDependencies() throws FileNotFoundException, IOException |
| { |
| Set<String> expandedModules = new HashSet<>(); |
| boolean done = false; |
| while (!done) |
| { |
| done = true; |
| Set<String> missingParents = new HashSet<>(); |
| |
| for (Module m : modules.values()) |
| { |
| for (String parent : m.getParentNames()) |
| { |
| String expanded = args.getProperties().expand(parent); |
| if (modules.containsKey(expanded) || missingModules.contains(parent) || expandedModules.contains(parent)) |
| { |
| continue; // found. skip it. |
| } |
| done = false; |
| StartLog.debug("Missing parent module %s == %s for %s",parent,expanded,m); |
| missingParents.add(parent); |
| } |
| } |
| |
| for (String missingParent : missingParents) |
| { |
| String expanded = args.getProperties().expand(missingParent); |
| Path file = baseHome.getPath("modules/" + expanded + ".mod"); |
| if (FS.canReadFile(file)) |
| { |
| Module module = registerModule(file); |
| updateParentReferencesTo(module); |
| if (!expanded.equals(missingParent)) |
| expandedModules.add(missingParent); |
| } |
| else |
| { |
| StartLog.debug("Missing module definition: %s == %s",missingParent,expanded); |
| missingModules.add(missingParent); |
| } |
| } |
| } |
| } |
| |
| private Module registerModule(Path file) throws FileNotFoundException, IOException |
| { |
| if (!FS.canReadFile(file)) |
| { |
| throw new IOException("Cannot read file: " + file); |
| } |
| StartLog.debug("Registering Module: %s",baseHome.toShortForm(file)); |
| Module module = new Module(baseHome,file); |
| return register(module); |
| } |
| |
| public Set<String> resolveChildModulesOf(String moduleName) |
| { |
| Set<Module> ret = new HashSet<>(); |
| Module module = get(moduleName); |
| findChildren(module,ret); |
| return asNameSet(ret); |
| } |
| |
| /** |
| * Resolve the execution order of the enabled modules, and all dependant modules, based on depth first transitive reduction. |
| * |
| * @return the list of active modules (plus dependant modules), in execution order. |
| */ |
| public List<Module> resolveEnabled() |
| { |
| Map<String, Module> active = new HashMap<String, Module>(); |
| |
| for (Module module : modules.values()) |
| { |
| if (module.isEnabled()) |
| { |
| findParents(module,active); |
| } |
| } |
| |
| /* |
| * check against the missing modules |
| * |
| * Ex: npn should match anything under npn/ |
| */ |
| for (String missing : missingModules) |
| { |
| for (String activeModule : active.keySet()) |
| { |
| if (missing.startsWith(activeModule)) |
| { |
| StartLog.warn("** Unable to continue, required dependency missing. [%s]",missing); |
| StartLog.warn("** As configured, Jetty is unable to start due to a missing enabled module dependency."); |
| StartLog.warn("** This may be due to a transitive dependency akin to spdy on npn, which resolves based on the JDK in use."); |
| throw new UsageException(UsageException.ERR_BAD_ARG, "Missing referenced dependency: " + missing); |
| } |
| } |
| } |
| |
| List<Module> ordered = new ArrayList<>(); |
| ordered.addAll(active.values()); |
| Collections.sort(ordered,new Module.DepthComparator()); |
| return ordered; |
| } |
| |
| public Set<String> resolveParentModulesOf(String moduleName) |
| { |
| Map<String, Module> ret = new HashMap<>(); |
| Module module = get(moduleName); |
| findParents(module,ret); |
| return ret.keySet(); |
| } |
| |
| private String toIndent(int depth) |
| { |
| char indent[] = new char[depth * 2]; |
| Arrays.fill(indent,' '); |
| return new String(indent); |
| } |
| |
| /** |
| * Modules can have a different logical name than to their filesystem reference. This updates existing references to the filesystem form to use the logical |
| * name form. |
| * |
| * @param module |
| * the module that might have other modules referring to it. |
| */ |
| private void updateParentReferencesTo(Module module) |
| { |
| if (module.getName().equals(module.getFilesystemRef())) |
| { |
| // nothing to do, its sane already |
| return; |
| } |
| |
| for (Module m : modules.values()) |
| { |
| Set<String> resolvedParents = new HashSet<>(); |
| for (String parent : m.getParentNames()) |
| { |
| if (parent.equals(module.getFilesystemRef())) |
| { |
| // use logical name instead |
| resolvedParents.add(module.getName()); |
| } |
| else |
| { |
| // use name as-is |
| resolvedParents.add(parent); |
| } |
| } |
| m.setParentNames(resolvedParents); |
| } |
| } |
| |
| @Override |
| public String toString() |
| { |
| StringBuilder str = new StringBuilder(); |
| str.append("Modules["); |
| str.append("count=").append(modules.size()); |
| str.append(",<"); |
| boolean delim = false; |
| for (String name : modules.keySet()) |
| { |
| if (delim) |
| { |
| str.append(','); |
| } |
| str.append(name); |
| delim = true; |
| } |
| str.append(">"); |
| str.append("]"); |
| return str.toString(); |
| } |
| } |