blob: b20f56523a93c59d663ff9f03276447773a7f547 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2015 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.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.CollationKey;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
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;
/**
* Represents a Module metadata, as defined in Jetty.
*/
public class Module
{
private static final String VERSION_UNSPECIFIED = "9.2";
/** The name of this Module (as a filesystem reference) */
private String fileRef;
/** The file of the module */
private final Path file;
/** The name of the module */
private String name;
/** The module description */
private List<String> description;
/** The version of Jetty the module supports */
private Version version;
/** List of xml configurations for this Module */
private List<String> xmls;
/** List of ini template lines */
private List<String> iniTemplate;
/** List of default config */
private List<String> defaultConfig;
/** List of library options for this Module */
private List<String> libs;
/** List of files for this Module */
private List<String> files;
/** List of selections for this Module */
private Set<String> selections;
/** Boolean true if directly enabled, false if selections are transitive */
private boolean enabled;
/** Skip File Validation (default: false) */
private boolean skipFilesValidation = false;
/** List of jvm Args */
private List<String> jvmArgs;
/** License lines */
private List<String> license;
/** Dependencies */
private Set<String> depends;
/** Optional */
private Set<String> optional;
public Module(BaseHome basehome, Path file) throws FileNotFoundException, IOException
{
super();
this.file = file;
// Strip .mod
this.fileRef = Pattern.compile(".mod$",Pattern.CASE_INSENSITIVE).matcher(file.getFileName().toString()).replaceFirst("");
name=fileRef;
init(basehome);
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 (fileRef == null)
{
if (other.fileRef != null)
{
return false;
}
}
else if (!fileRef.equals(other.fileRef))
{
return false;
}
return true;
}
public void expandProperties(Props props)
{
Function<String,String> expander = d->{return props.expand(d);};
depends=depends.stream().map(expander).collect(Collectors.toSet());
optional=optional.stream().map(expander).collect(Collectors.toSet());
}
public List<String> getDefaultConfig()
{
return defaultConfig;
}
public List<String> getIniTemplate()
{
return iniTemplate;
}
public List<String> getFiles()
{
return files;
}
public boolean isSkipFilesValidation()
{
return skipFilesValidation;
}
public String getFilesystemRef()
{
return fileRef;
}
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()
{
final int prime = 31;
int result = 1;
result = (prime * result) + ((fileRef == null)?0:fileRef.hashCode());
return result;
}
public boolean hasLicense()
{
return (license != null) && (license.size() > 0);
}
private void init(BaseHome basehome)
{
description = new ArrayList<>();
xmls = new ArrayList<>();
defaultConfig = new ArrayList<>();
iniTemplate = new ArrayList<>();
libs = new ArrayList<>();
files = new ArrayList<>();
jvmArgs = new ArrayList<>();
license = new ArrayList<>();
depends = new HashSet<>();
optional = new HashSet<>();
selections = new HashSet<>();
String name = basehome.toShortForm(file);
// Find module system name (usually in the form of a filesystem reference)
Pattern pat = Pattern.compile("^.*[/\\\\]{1}modules[/\\\\]{1}(.*).mod$",Pattern.CASE_INSENSITIVE);
Matcher mat = pat.matcher(name);
if (!mat.find())
{
throw new RuntimeException("Invalid Module location (must be located under /modules/ directory): " + name);
}
this.fileRef = mat.group(1).replace('\\','/');
this.name=this.fileRef;
}
/**
* Indicates a module that is dynamic in nature
*
* @return a module where the declared metadata name does not match the filename reference (aka a dynamic module)
*/
public boolean isDynamic()
{
return !name.equals(fileRef);
}
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(file))
{
StartLog.debug("Skipping read of missing file: %s",basehome.toShortForm(file));
return;
}
try (BufferedReader buf = Files.newBufferedReader(file,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":
depends.add(line);
break;
case "FILES":
files.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":
libs.add(line);
break;
case "LICENSE":
case "LICENCE":
license.add(line);
break;
case "NAME":
name=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 void setEnabled(boolean enabled)
{
throw new RuntimeException("Don't enable directly");
}
public void setSkipFilesValidation(boolean skipFilesValidation)
{
this.skipFilesValidation = skipFilesValidation;
}
@Override
public String toString()
{
StringBuilder str = new StringBuilder();
str.append("Module[").append(getName());
if (isDynamic())
{
str.append(",file=").append(fileRef);
}
if (isSelected())
{
str.append(",selected");
}
if (isTransitive())
{
str.append(",transitive");
}
str.append(']');
return str.toString();
}
public Set<String> getDepends()
{
return Collections.unmodifiableSet(depends);
}
public Set<String> getOptional()
{
return Collections.unmodifiableSet(optional);
}
public List<String> getDescription()
{
return description;
}
public boolean isSelected()
{
return !selections.isEmpty();
}
public Set<String> getSelections()
{
return Collections.unmodifiableSet(selections);
}
public boolean addSelection(String enabledFrom,boolean transitive)
{
boolean updated=selections.isEmpty();
if (transitive)
{
if (!enabled)
selections.add(enabledFrom);
}
else
{
if (!enabled)
{
updated=true;
selections.clear(); // clear any transitive enabling
}
enabled=true;
selections.add(enabledFrom);
}
return updated;
}
public boolean isTransitive()
{
return isSelected() && !enabled;
}
}