//
// ========================================================================
// 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.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
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.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.stream.Collectors;
import java.util.stream.Stream;
/**
* Access for all modules declared, as well as what is enabled.
*/
public class Modules implements Iterable<Module>
{
private final List<Module> _modules = new ArrayList<>();
private final Map<String,Module> _names = new HashMap<>();
private final Map<String,Set<Module>> _provided = 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.getProperties().containsKey("java.version"))
{
String java_version = System.getProperty("java.version");
if (java_version!=null)
{
args.setProperty("java.version",java_version,"<internal>");
}
}
try
{
Path deprecated_path = _baseHome.getPath("modules/deprecated.properties");
if (deprecated_path!=null && FS.exists(deprecated_path))
{
_deprecated.load(new FileInputStream(deprecated_path.toFile()));
}
}
catch (IOException e)
{
StartLog.debug(e);
}
}
public void dump(List<String> tags)
{
Set<String> exclude = tags.stream().filter(t->t.startsWith("-")).map(t->t.substring(1)).collect(Collectors.toSet());
Set<String> include = tags.stream().filter(t->!t.startsWith("-")).collect(Collectors.toSet());
boolean all = include.contains("*") || include.isEmpty();
AtomicReference<String> tag = new AtomicReference<>();
_modules.stream()
.filter(m->
{
boolean included = all || m.getTags().stream().anyMatch(t->include.contains(t));
boolean excluded = m.getTags().stream().anyMatch(t->exclude.contains(t));
return included && !excluded;
})
.sorted()
.forEach(module->
{
if (!module.getPrimaryTag().equals(tag.get()))
{
tag.set(module.getPrimaryTag());
System.out.printf("%nModules for tag '%s':%n",module.getPrimaryTag());
System.out.print("-------------------");
for (int i=module.getPrimaryTag().length();i-->0;)
System.out.print("-");
System.out.println();
}
String label;
Set<String> provides = module.getProvides();
provides.remove(module.getName());
System.out.printf("%n Module: %s %s%n",module.getName(),provides.size()>0?provides:"");
for (String description : module.getDescription())
{
System.out.printf(" : %s%n",description);
}
if (!module.getTags().isEmpty())
{
label=" Tags: %s";
for (String t : module.getTags())
{
System.out.printf(label,t);
label=", %s";
}
System.out.println();
}
if (!module.getDepends().isEmpty())
{
label=" Depend: %s";
for (String parent : module.getDepends())
{
System.out.printf(label,parent);
label=", %s";
}
System.out.println();
}
if (!module.getOptional().isEmpty())
{
label=" Optional: %s";
for (String parent : module.getOptional())
{
System.out.printf(label,parent);
label=", %s";
}
System.out.println();
}
for (String lib : module.getLibs())
{
System.out.printf(" LIB: %s%n",lib);
}
for (String xml : module.getXmls())
{
System.out.printf(" XML: %s%n",xml);
}
for (String jvm : module.getJvmArgs())
{
System.out.printf(" JVM: %s%n",jvm);
}
if (module.isEnabled())
{
for (String selection : module.getEnableSources())
{
System.out.printf(" Enabled: %s%n",selection);
}
}
});
}
public void dumpEnabled()
{
int i=0;
List<Module> enabled = getEnabled();
for (Module module:enabled)
{
String name=module.getName();
String index=(i++)+")";
for (String s:module.getEnableSources())
{
System.out.printf(" %4s %-15s %s%n",index,name,s);
index="";
name="";
}
if (module.isTransitive() && module.hasIniTemplate())
System.out.printf(" init template available with --add-to-start=%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->{
_provided.computeIfAbsent(n,k->new HashSet<Module>()).add(module);
});
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<Module> getEnabled()
{
List<Module> enabled = _modules.stream().filter(m->{return m.isEnabled();}).collect(Collectors.toList());
TopologicalSort<Module> sort = new TopologicalSort<>();
for (Module module: enabled)
{
Consumer<String> add = name ->
{
Module dependency = _names.get(name);
if (dependency!=null && dependency.isEnabled())
sort.addDependency(module,dependency);
Set<Module> provided = _provided.get(name);
if (provided!=null)
for (Module p : provided)
if (p.isEnabled())
sort.addDependency(module,p);
};
module.getDepends().forEach(add);
module.getOptional().forEach(add);
}
sort.sort(enabled);
return enabled;
}
/** 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<String> 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<String> enabled = new HashSet<>();
enable(enabled,module,enabledFrom,false);
return enabled;
}
private void enable(Set<String> 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("Cycle at %s",module);
return;
}
// Check that this is not already provided by another module!
for (String name:module.getProvides())
{
Set<Module> providers = _provided.get(name);
if (providers!=null)
{
for (Module p:providers)
{
if (p!=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.getProperties());
// Apply default configuration
if (module.hasDefaultConfig())
{
for(String line:module.getDefaultConfig())
_args.parse(line,module.getName()+"[ini]");
for (Module m:_modules)
m.expandDependencies(_args.getProperties());
}
}
// Process module dependencies (always processed as may be dynamic)
StartLog.debug("Enabled module %s depends on %s",module.getName(),module.getDepends());
for(String dependsOn:module.getDepends())
{
// Look for modules that provide that dependency
Set<Module> providers = getAvailableProviders(dependsOn);
StartLog.debug("Module %s depends on %s provided by %s",module,dependsOn,providers);
// If there are no known providers of the module
if (providers.isEmpty())
{
// look for a dynamic module
if (dependsOn.contains("/"))
{
Path file = _baseHome.getPath("modules/" + dependsOn + ".mod");
registerModule(file).expandDependencies(_args.getProperties());
providers = _provided.get(dependsOn);
if (providers==null || providers.isEmpty())
throw new UsageException("Module %s does not provide %s",_baseHome.toShortForm(file),dependsOn);
enable(newlyEnabled,providers.stream().findFirst().get(),"dynamic dependency of "+module.getName(),true);
continue;
}
throw new UsageException("No module found to provide %s for %s",dependsOn,module);
}
// If a provider is already enabled, then add a transitive enable
if (providers.stream().filter(Module::isEnabled).count()!=0)
providers.stream().filter(m->m.isEnabled()&&m!=module).forEach(m->enable(newlyEnabled,m,"transitive provider of "+dependsOn+" for "+module.getName(),true));
else
{
// Is there an obvious default?
Optional<Module> dftProvider = (providers.size()==1)
?providers.stream().findFirst()
:providers.stream().filter(m->m.getName().equals(dependsOn)).findFirst();
if (dftProvider.isPresent())
enable(newlyEnabled,dftProvider.get(),"transitive provider of "+dependsOn+" for "+module.getName(),true);
else if (StartLog.isDebugEnabled())
StartLog.debug("Module %s requires a %s implementation from one of %s",module,dependsOn,providers);
}
}
}
private Set<Module> getAvailableProviders(String name)
{
// Get all available providers
Set<Module> providers = _provided.get(name);
StartLog.debug("Providers of %s are %s",name,providers);
if (providers==null || providers.isEmpty())
return Collections.emptySet();
providers = new HashSet<>(providers);
// find all currently provided names by other modules
Set<String> 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<Module> 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<Module> iterator()
{
return _modules.iterator();
}
public Stream<Module> stream()
{
return _modules.stream();
}
public void checkEnabledModules()
{
StringBuilder unsatisfied=new StringBuilder();
_modules.stream().filter(Module::isEnabled).forEach(m->
{
// Check dependencies
m.getDepends().forEach(d->
{
Set<Module> providers = getAvailableProviders(d);
if (providers.stream().filter(Module::isEnabled).count()==0)
{
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);
}
}