org.eclipse.jetty.start.Modules Maven / Gradle / Ivy
The newest version!
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.start;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jetty.util.TopologicalSort;
/**
* Access for all modules declared, as well as what is enabled.
*/
public class Modules implements Iterable
{
private final List _modules = new ArrayList<>();
private final Map _names = new HashMap<>();
private final Map> _provided = new HashMap<>();
private final Map _providedDefaults = new HashMap<>();
private final BaseHome _baseHome;
private final StartArgs _args;
private final Properties _deprecated = new Properties();
public Modules(BaseHome basehome, StartArgs args)
{
this._baseHome = basehome;
this._args = args;
// Allow override mostly for testing
if (!args.getJettyEnvironment().getProperties().containsKey("java.version"))
{
String javaVersion = System.getProperty("java.version");
if (javaVersion != null)
{
// TODO environment
args.setProperty(null, "java.version", javaVersion, "");
}
}
try
{
Path deprecatedPath = _baseHome.getPath("modules/deprecated.properties");
if (deprecatedPath != null && FS.exists(deprecatedPath))
{
try (InputStream inputStream = Files.newInputStream(deprecatedPath))
{
_deprecated.load(inputStream);
}
}
}
catch (IOException e)
{
StartLog.debug(e);
}
}
public void showModules(PrintStream out, List modules)
{
Stream stream = (modules.contains("*") || modules.isEmpty())
? _modules.stream().sorted()
: modules.stream().map(this::get);
stream.forEach(module ->
{
if (module == null)
return;
String label;
Set provides = module.getProvides();
provides.remove(module.getName());
out.printf("%n Module: %s %s%n", module.getName(), provides.size() > 0 ? provides : "");
for (String description : module.getDescription())
{
out.printf(" : %s%n", description);
}
if (module.getEnvironment() != null)
{
out.printf("Environment: %s%n", module.getEnvironment());
}
if (!module.getTags().isEmpty())
{
label = " Tags: %s";
for (String t : module.getTags())
{
out.printf(label, t);
label = ", %s";
}
out.println();
}
if (!module.getDepends().isEmpty())
{
label = " Depend: %s";
for (String parent : module.getDepends())
{
parent = Module.normalizeModuleName(parent);
out.printf(label, parent);
if (Module.isConditionalDependency(parent))
out.print(" [conditional]");
label = ", %s";
}
out.println();
}
if (!module.getBefore().isEmpty())
{
label = " Before: %s";
for (String before : module.getBefore())
{
out.printf(label, before);
label = ", %s";
}
out.println();
}
if (!module.getAfter().isEmpty())
{
label = " After: %s";
for (String after : module.getAfter())
{
out.printf(label, after);
label = ", %s";
}
out.println();
}
for (String lib : module.getLibs())
{
out.printf(" LIB: %s%n", lib);
}
for (String xml : module.getXmls())
{
out.printf(" XML: %s%n", xml);
}
for (String jpms : module.getJPMS())
{
out.printf(" JPMS: %s%n", jpms);
}
for (String jvm : module.getJvmArgs())
{
out.printf(" JVM: %s%n", jvm);
}
if (module.isEnabled())
{
for (String selection : module.getEnableSources())
{
out.printf(" Enabled: %s%n", selection);
}
}
});
}
public void listModules(PrintStream out, List tags)
{
if (tags.contains("-*"))
return;
tags = new ArrayList<>(tags);
boolean wild = tags.contains("*");
Set included = new HashSet<>();
if (wild)
tags.remove("*");
else
tags.stream().filter(t -> !t.startsWith("-")).forEach(included::add);
Set excluded = new HashSet<>();
tags.stream().filter(t -> t.startsWith("-")).map(t -> t.substring(1)).forEach(excluded::add);
if (!included.contains("internal"))
excluded.add("internal");
Predicate filter = m -> (included.isEmpty() || m.getTags().stream().anyMatch(included::contains)) &&
m.getTags().stream().noneMatch(excluded::contains);
Optional max = _modules.stream().filter(filter).map(Module::getName).map(String::length).max(Integer::compareTo);
if (max.isEmpty())
return;
String format = "%" + max.get() + "s - %s%n";
Comparator comparator = wild ? Comparator.comparing(Module::getName) : Module::compareTo;
AtomicReference tag = new AtomicReference<>();
_modules.stream().filter(filter).sorted(comparator).forEach(module ->
{
if (!wild && !module.getPrimaryTag().equals(tag.get()))
{
tag.set(module.getPrimaryTag());
out.printf("%n%s modules:", module.getPrimaryTag());
out.printf("%n%s---------%n", "-".repeat(module.getPrimaryTag().length()));
}
List description = module.getDescription();
out.printf(format, module.getName(), description != null && description.size() > 0 ? description.get(0) : "");
});
}
public void listEnabled(PrintStream out)
{
out.println();
out.println("Enabled Modules:");
out.println("----------------");
int i = 0;
List enabled = getEnabled();
for (Module module : enabled)
{
String index = (i++) + ")";
String name = module.getName();
if (!module.getDeprecated().isEmpty())
name += " (deprecated)";
for (String s : module.getEnableSources())
{
out.printf("%4s %-25s %s%n", index, name, s);
index = "";
name = "";
}
if (module.isTransitive() && module.hasIniTemplate())
out.printf(" ".repeat(31) + "ini template available with --add-modules=%s%n", module.getName());
}
}
public void registerAll() throws IOException
{
for (Path path : _baseHome.getPaths("modules/*.mod"))
{
registerModule(path);
}
}
private Module registerModule(Path file)
{
if (!FS.canReadFile(file))
{
throw new IllegalStateException("Cannot read file: " + file);
}
String shortName = _baseHome.toShortForm(file);
try
{
StartLog.debug("Registering Module: %s", shortName);
Module module = new Module(_baseHome, file);
_modules.add(module);
_names.put(module.getName(), module);
module.getProvides().forEach(n ->
{
// Syntax can be :
// "" - for a simple provider reference
// "|default" - for a provider that is also the default implementation
String name = n;
boolean isDefaultProvider = false;
int idx = n.indexOf('|');
if (idx > 0)
{
name = n.substring(0, idx);
isDefaultProvider = n.substring(idx + 1).equalsIgnoreCase("default");
}
_provided.computeIfAbsent(name, k -> new HashSet<>()).add(module);
if (isDefaultProvider)
{
_providedDefaults.computeIfAbsent(name, k -> module.getName());
}
});
return module;
}
catch (Error | RuntimeException t)
{
throw t;
}
catch (Throwable t)
{
throw new IllegalStateException("Unable to register module: " + shortName, t);
}
}
@Override
public String toString()
{
StringBuilder str = new StringBuilder();
str.append("Modules[");
str.append("count=").append(_modules.size());
str.append(",<");
final AtomicBoolean delim = new AtomicBoolean(false);
_modules.forEach(m ->
{
if (delim.get())
str.append(',');
str.append(m.getName());
delim.set(true);
});
str.append(">");
str.append("]");
return str.toString();
}
public List getEnabled()
{
List enabled = _modules.stream().filter(Module::isEnabled).collect(Collectors.toList());
TopologicalSort sort = new TopologicalSort<>();
for (Module module : enabled)
{
Consumer add = name ->
{
Module dependency = _names.get(name);
if (dependency != null && dependency.isEnabled())
sort.addDependency(module, dependency);
Set provided = _provided.get(name);
if (provided != null)
{
for (Module p : provided)
{
if (p.isEnabled())
sort.addDependency(module, p);
}
}
};
module.getDepends().forEach(add);
module.getAfter().forEach(add);
module.getBefore().forEach(name ->
{
Module before = _names.get(name);
if (before != null && before.isEnabled())
sort.addDependency(before, module);
});
}
sort.sort(enabled);
return enabled;
}
public List getSortedAll()
{
List all = new ArrayList<>(_modules);
TopologicalSort sort = new TopologicalSort<>();
for (Module module : all)
{
Consumer add = name ->
{
Module dependency = _names.get(name);
if (dependency != null)
sort.addDependency(module, dependency);
Set provided = _provided.get(name);
if (provided != null)
{
for (Module p : provided)
{
sort.addDependency(module, p);
}
}
};
module.getDepends().forEach(add);
module.getAfter().forEach(add);
module.getBefore().forEach(name ->
{
Module before = _names.get(name);
if (before != null)
sort.addDependency(before, module);
});
}
sort.sort(all);
return all;
}
public List getSortedNames(Set enabledModules)
{
return getSortedAll().stream()
.map(Module::getName)
.filter(enabledModules::contains)
.collect(Collectors.toList());
}
/**
* Enable a module
*
* @param name The name of the module to enable
* @param enabledFrom The source the module was enabled from
* @return The set of modules newly enabled
*/
public Set enable(String name, String enabledFrom)
{
Module module = get(name);
if (module == null)
throw new UsageException(UsageException.ERR_UNKNOWN, "Unknown module='%s'. List available with --list-modules", name);
Set enabled = new HashSet<>();
enable(enabled, module, enabledFrom, false);
return enabled;
}
private void enable(Set newlyEnabled, Module module, String enabledFrom, boolean transitive)
{
StartLog.debug("Enable [%s] from [%s] transitive=%b", module, enabledFrom, transitive);
if (newlyEnabled.contains(module.getName()))
{
StartLog.debug("Already enabled [%s] from %s", module.getName(), module.getEnableSources());
return;
}
List deprecated = module.getDeprecated();
if (!deprecated.isEmpty())
{
String reason = deprecated.stream().collect(Collectors.joining(System.lineSeparator()));
StartLog.warn(reason);
}
// Check that this is not already provided by another module!
for (String name : module.getProvides())
{
Set providers = _provided.get(name);
if (providers != null)
{
for (Module p : providers)
{
if (!p.equals(module) && p.isEnabled())
{
// If the already enabled module is transitive and this enable is not
if (p.isTransitive() && !transitive)
p.clearTransitiveEnable();
else
throw new UsageException("Module %s provides %s, which is already provided by %s enabled in %s", module.getName(), name, p.getName(), p.getEnableSources());
}
}
}
}
// Enable the module
if (module.enable(enabledFrom, transitive))
{
StartLog.debug("Enabled [%s]", module.getName());
newlyEnabled.add(module.getName());
// Expand module properties
module.expandDependencies(_args.getJettyEnvironment().getProperties());
// Apply default configuration
if (module.hasDefaultConfig())
{
String source = module.getName() + "[ini]";
StartEnvironment environment = _args.getJettyEnvironment();
environment = _args.parse(environment, "--module=" + module.getName(), source);
for (String line : module.getIniSection())
environment = _args.parse(environment, line, source);
for (Module m : _modules)
m.expandDependencies(environment.getProperties());
}
}
// Process module dependencies (always processed as may be dynamic)
StartLog.debug("Enabled module [%s] depends on %s", module.getName(), module.getDepends());
for (String dependsOnRaw : module.getDepends())
{
boolean isConditional = Module.isConditionalDependency(dependsOnRaw);
// Final to allow lambda's below to use name
final String dependentModule = Module.normalizeModuleName(dependsOnRaw);
// Look for modules that provide that dependency
Set providers = getAvailableProviders(dependentModule);
StartLog.debug("Module [%s] depends on [%s] provided by %s", module, dependentModule, providers);
// If there are no known providers of the module
if (providers.isEmpty())
{
// look for a dynamic module
if (dependentModule.contains("/"))
{
Path file = _baseHome.getPath("modules/" + dependentModule + ".mod");
if (!isConditional || Files.exists(file))
{
registerModule(file).expandDependencies(_args.getJettyEnvironment().getProperties());
providers = _provided.get(dependentModule);
if (providers == null || providers.isEmpty())
throw new UsageException("Module %s does not provide %s", _baseHome.toShortForm(file), dependentModule);
enable(newlyEnabled, providers.stream().findFirst().get(), "dynamic dependency of " + module.getName(), true);
continue;
}
}
// is this a conditional module
if (isConditional)
{
StartLog.debug("Skipping conditional module [%s]: it does not exist", dependentModule);
continue;
}
// throw an exception (not a dynamic module and a required dependency)
throw new UsageException("No module found to provide %s for %s", dependentModule, module);
}
// If a provider is already enabled, then add a transitive enable
if (providers.stream().anyMatch(Module::isEnabled))
providers.stream().filter(m -> m.isEnabled() && !m.equals(module)).forEach(m -> enable(newlyEnabled, m, "transitive provider of " + dependentModule + " for " + module.getName(), true));
else
{
Optional dftProvider = findDefaultProvider(providers, dependentModule);
if (dftProvider.isPresent())
{
StartLog.debug("Using [%s] provider as default for [%s]", dftProvider.get(), dependentModule);
enable(newlyEnabled, dftProvider.get(), "transitive provider of " + dependentModule + " for " + module.getName(), true);
}
}
}
}
private Optional findDefaultProvider(Set providers, String dependsOn)
{
// Is it obvious?
if (providers.size() == 1)
return providers.stream().findFirst();
// If more then one provider impl, is there one specified as "default"?
if (providers.size() > 1)
{
// Was it specified with [provides] "name|default" ?
String defaultProviderName = _providedDefaults.get(dependsOn);
if (defaultProviderName != null)
{
return providers.stream().filter(m -> m.getName().equals(defaultProviderName)).findFirst();
}
// Or does a module exist with the same name as the [provides] "name"
return providers.stream().filter(m -> m.getName().equals(dependsOn)).findFirst();
}
// No default provider
return Optional.empty();
}
private Set getAvailableProviders(String name)
{
// Get all available providers
Set providers = _provided.get(name);
StartLog.debug("Providers of [%s] are %s", name, providers);
if (providers == null || providers.isEmpty())
return Set.of();
providers = new HashSet<>(providers);
// find all currently provided names by other modules
Set provided = new HashSet<>();
for (Module m : _modules)
{
if (m.isEnabled())
{
provided.add(m.getName());
provided.addAll(m.getProvides());
}
}
// Remove any that cannot be selected
for (Iterator i = providers.iterator(); i.hasNext(); )
{
Module provider = i.next();
if (!provider.isEnabled())
{
for (String p : provider.getProvides())
{
if (provided.contains(p))
{
StartLog.debug("Removing provider %s because %s already enabled", provider, p);
i.remove();
break;
}
}
}
}
StartLog.debug("Available providers of [%s] are %s", name, providers);
return providers;
}
public Module get(String name)
{
Module module = _names.get(name);
if (module == null)
{
String reason = _deprecated.getProperty(name);
if (reason != null)
StartLog.warn("Module %s is no longer available: %s", name, reason);
}
return module;
}
@Override
public Iterator iterator()
{
return _modules.iterator();
}
public Stream stream()
{
return _modules.stream();
}
public void checkEnabledModules()
{
StringBuilder unsatisfied = new StringBuilder();
_modules.stream().filter(Module::isEnabled).forEach(m ->
{
// Check dependencies
m.getDepends().stream()
.filter(depends -> !Module.isConditionalDependency(depends))
.forEach(d ->
{
Set providers = getAvailableProviders(d);
if (providers.stream().noneMatch(Module::isEnabled))
{
if (unsatisfied.length() > 0)
unsatisfied.append(',');
unsatisfied.append(m.getName());
StartLog.error("Module [%s] requires a module providing [%s] from one of %s%n", m.getName(), d, providers);
}
});
});
if (unsatisfied.length() > 0)
throw new UsageException(-1, "Unsatisfied module dependencies: " + unsatisfied);
}
}