// // ======================================================================== // Copyright (c) 1995-2017 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.BufferedReader; import java.io.BufferedWriter; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.eclipse.jetty.start.Props.Prop; import org.eclipse.jetty.start.config.CommandLineConfigSource; /** * Represents a Module metadata, as defined in Jetty. * * <p>A module consists of: * <ul> * <li>A set of jar files, directories and/or jar file patterns to be added to the classpath</li> * <li>A list of XML configuration files</li> * <li>Properties set either directly or via a file of properties</li> * <li>A set of modules names (or capability names) that this module depends on.</li> * <li>A set of capability names that this module provides (including it's own name).</li> * <li>Licence details for using the module</li> * </ul> * Modules are discovered in the <code>${jetty.home}/modules</code> and * <code>${jetty.home}/modules</code> directories. A module may refer to * non-discovered dynamic module in a subdirectory, using a property as part or * all of the name. * A module may be enabled, either directly by name or transiently via a dependency * from another module by name or provided capability. */ public class Module implements Comparable<Module> { private static final String VERSION_UNSPECIFIED = "9.2"; static Pattern MOD_NAME = Pattern.compile("^(.*)\\.mod",Pattern.CASE_INSENSITIVE); static Pattern SET_PROPERTY = Pattern.compile("^(#?)\\s*([^=\\s]+)=(.*)$"); /** The file of the module */ private final Path _path; /** The name of the module */ private final String _name; /** Is the module dynamic - ie referenced rather than discovered */ private final boolean _dynamic; /** The version of Jetty the module supports */ private Version version; /** The module description */ private final List<String> _description=new ArrayList<>(); /** List of xml configurations for this Module */ private final List<String> _xmls=new ArrayList<>(); /** List of ini template lines */ private final List<String> _iniTemplate=new ArrayList<>(); /** List of default config */ private final List<String> _defaultConfig=new ArrayList<>(); /** List of library options for this Module */ private final List<String> _libs=new ArrayList<>(); /** List of files for this Module */ private final List<String> _files=new ArrayList<>(); /** List of selections for this Module */ private final Set<String> _enables=new HashSet<>(); /** List of provides for this Module */ private final Set<String> _provides=new HashSet<>(); /** List of tags for this Module */ private final List<String> _tags=new ArrayList<>(); /** Boolean true if directly enabled, false if all selections are transitive */ private boolean _notTransitive; /** Skip File Validation (default: false) */ private boolean _skipFilesValidation = false; /** List of jvm Args */ private final List<String> _jvmArgs=new ArrayList<>(); /** License lines */ private final List<String> _license=new ArrayList<>(); /** Dependencies */ private final List<String> _depends=new ArrayList<>(); /** Optional */ private final Set<String> _optional=new HashSet<>(); public Module(BaseHome basehome, Path path) throws FileNotFoundException, IOException { super(); _path = path; // Module name is the / separated path below the modules directory int m=-1; for (int i=path.getNameCount();i-->0;) { if ("modules".equals(path.getName(i).toString())) { m=i; break; } } if (m<0) throw new IllegalArgumentException("Module not contained within modules directory: "+basehome.toShortForm(path)); String n=path.getName(m+1).toString(); for (int i=m+2;i<path.getNameCount();i++) n=n+"/"+path.getName(i).toString(); Matcher matcher=MOD_NAME.matcher(n); if (!matcher.matches()) throw new IllegalArgumentException("Module filename must have .mod extension: "+basehome.toShortForm(path)); _name=matcher.group(1); _provides.add(_name); _dynamic=_name.contains("/"); process(basehome); } public String getName() { return _name; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Module other = (Module)obj; if (_path == null) return other._path == null; return _path.equals(other._path); } public void expandDependencies(Props props) { Function<String,String> expander = d->{return props.expand(d);}; List<String> tmp=_depends.stream().map(expander).collect(Collectors.toList()); _depends.clear(); _depends.addAll(tmp); tmp=_optional.stream().map(expander).collect(Collectors.toList()); _optional.clear(); _optional.addAll(tmp); } public List<String> getDefaultConfig() { return _defaultConfig; } public List<String> getIniTemplate() { return _iniTemplate; } public List<String> getFiles() { return _files; } public boolean isSkipFilesValidation() { return _skipFilesValidation; } public List<String> getJvmArgs() { return _jvmArgs; } public List<String> getLibs() { return _libs; } public List<String> getLicense() { return _license; } public List<String> getXmls() { return _xmls; } public Version getVersion() { return version; } public boolean hasDefaultConfig() { return !_defaultConfig.isEmpty(); } public boolean hasIniTemplate() { return !_iniTemplate.isEmpty(); } @Override public int hashCode() { return _name.hashCode(); } public boolean hasLicense() { return (_license != null) && (_license.size() > 0); } /** * Indicates a module that is dynamic in nature * * @return a module where the name is not in the top level of the modules directory */ public boolean isDynamic() { return _dynamic; } public boolean hasFiles(BaseHome baseHome, Props props) { for (String ref : getFiles()) { FileArg farg = new FileArg(this,props.expand(ref)); Path refPath = baseHome.getBasePath(farg.location); if (!Files.exists(refPath)) { return false; } } return true; } public void process(BaseHome basehome) throws FileNotFoundException, IOException { Pattern section = Pattern.compile("\\s*\\[([^]]*)\\]\\s*"); if (!FS.canReadFile(_path)) { StartLog.debug("Skipping read of missing file: %s",basehome.toShortForm(_path)); return; } try (BufferedReader buf = Files.newBufferedReader(_path,StandardCharsets.UTF_8)) { String sectionType = ""; String line; while ((line = buf.readLine()) != null) { line = line.trim(); Matcher sectionMatcher = section.matcher(line); if (sectionMatcher.matches()) { sectionType = sectionMatcher.group(1).trim().toUpperCase(Locale.ENGLISH); } else { // blank lines and comments are valid for ini-template section if ((line.length() == 0) || line.startsWith("#")) { // Remember ini comments and whitespace (empty lines) // for the [ini-template] section if ("INI-TEMPLATE".equals(sectionType)) { _iniTemplate.add(line); } } else { switch (sectionType) { case "": // ignore (this would be entries before first section) break; case "DESCRIPTION": _description.add(line); break; case "DEPEND": case "DEPENDS": if (!_depends.contains(line)) _depends.add(line); break; case "FILE": case "FILES": _files.add(line); break; case "TAG": case "TAGS": _tags.add(line); break; case "DEFAULTS": // old name introduced in 9.2.x case "INI": // new name for 9.3+ _defaultConfig.add(line); break; case "INI-TEMPLATE": _iniTemplate.add(line); break; case "LIB": case "LIBS": _libs.add(line); break; case "LICENSE": case "LICENSES": case "LICENCE": case "LICENCES": _license.add(line); break; case "NAME": StartLog.warn("Deprecated [name] used in %s",basehome.toShortForm(_path)); _provides.add(line); break; case "PROVIDE": case "PROVIDES": _provides.add(line); break; case "OPTIONAL": _optional.add(line); break; case "EXEC": _jvmArgs.add(line); break; case "VERSION": if (version != null) { throw new IOException("[version] already specified"); } version = new Version(line); break; case "XML": _xmls.add(line); break; default: throw new IOException("Unrecognized module section: [" + sectionType + "]"); } } } } } if (version == null) { version = new Version(VERSION_UNSPECIFIED); } } public boolean clearTransitiveEnable() { if (_notTransitive) throw new IllegalStateException("Not Transitive"); if (isEnabled()) { _enables.clear(); return true; } return false; } public void setSkipFilesValidation(boolean skipFilesValidation) { this._skipFilesValidation = skipFilesValidation; } @Override public String toString() { StringBuilder str = new StringBuilder(); str.append(getName()); char sep='{'; if (isDynamic()) { str.append(sep).append("dynamic"); sep=','; } if (isEnabled()) { str.append(sep).append("enabled"); sep=','; } if (isTransitive()) { str.append(sep).append("transitive"); sep=','; } if (sep!='{') str.append('}'); return str.toString(); } public List<String> getDepends() { return new ArrayList<>(_depends); } public Set<String> getProvides() { return new HashSet<>(_provides); } public Set<String> getOptional() { return new HashSet<>(_optional); } public List<String> getDescription() { return _description; } public List<String> getTags() { return _tags; } public String getPrimaryTag() { return _tags.isEmpty()?"*":_tags.get(0); } public boolean isEnabled() { return !_enables.isEmpty(); } public Set<String> getEnableSources() { return new HashSet<>(_enables); } /** * @param source String describing where the module was enabled from * @param transitive True if the enable is transitive * @return true if the module was not previously enabled */ public boolean enable(String source,boolean transitive) { boolean updated=_enables.isEmpty(); if (transitive) { // Ignore transitive selections if explicitly enabled if (!_notTransitive) _enables.add(source); } else { if (!_notTransitive) { // Ignore transitive selections if explicitly enabled updated=true; _enables.clear(); // clear any transitive enabling } _notTransitive=true; _enables.add(source); } return updated; } public boolean isTransitive() { return isEnabled() && !_notTransitive; } public void writeIniSection(BufferedWriter writer, Props props) { PrintWriter out = new PrintWriter(writer); out.println("# --------------------------------------- "); out.println("# Module: " + getName()); for (String line : getDescription()) out.append("# ").println(line); out.println("# --------------------------------------- "); out.println("--module=" + getName()); out.println(); for (String line : getIniTemplate()) { Matcher m = SET_PROPERTY.matcher(line); if (m.matches() && m.groupCount()==3) { String name = m.group(2); Prop p = props.getProp(name); if (p!=null && p.origin.startsWith(CommandLineConfigSource.ORIGIN_CMD_LINE)) { StartLog.info("%-15s property set %s=%s",this._name,name,p.value); out.printf("%s=%s%n",name,p.value); } else out.println(line); } else out.println(line); } out.println(); out.flush(); } @Override public int compareTo(Module m) { int by_tag = getPrimaryTag().compareTo(m.getPrimaryTag()); if (by_tag!=0) return by_tag; return getName().compareTo(m.getName()); } }