org.eclipse.jetty.webapp.Configurations Maven / Gradle / Ivy
Show all versions of jetty-webapp Show documentation
//
// ========================================================================
// 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.webapp;
import java.io.IOException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.TopologicalSort;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An ordered list of {@link Configuration} instances.
*
* The ordering of Configurations will initially be the order in which they
* are added. The {@link #sort()} method can be used to apply a
* {@link TopologicalSort} to the ordering as defined by the
* {@link Configuration#getDependencies()} and
* {@link Configuration#getDependents()} methods.
* Instances that do not have ordering dependencies will maintain
* their add order, as will additions/insertions made after the
* the sort.
*
*
* If an added {@link Configuration} returns a value for
* {@link Configuration#replaces()} then the added instance will replace
* any existing instance of that type or that has already replaced that
* type.
*
*/
public class Configurations extends AbstractList implements Dumpable
{
private static final Logger LOG = LoggerFactory.getLogger(Configurations.class);
private static final AutoLock __lock = new AutoLock();
private static final List __known = new ArrayList<>();
private static final List __unavailable = new ArrayList<>();
private static final Set __knownByClassName = new HashSet<>();
public static List getKnown()
{
try (AutoLock l = __lock.lock())
{
if (__known.isEmpty())
{
TypeUtil.serviceProviderStream(ServiceLoader.load(Configuration.class)).forEach(provider ->
{
try
{
Configuration configuration = provider.get();
if (!configuration.isAvailable())
{
if (LOG.isDebugEnabled())
LOG.debug("Configuration unavailable: {}", configuration);
__unavailable.add(configuration);
return;
}
__known.add(configuration);
__knownByClassName.add(configuration.getClass().getName());
}
catch (Throwable e)
{
LOG.warn("Unable to get known Configuration", e);
}
});
sort(__known);
if (LOG.isDebugEnabled())
{
for (Configuration c : __known)
{
LOG.debug("known {}", c);
}
LOG.debug("Known Configurations {}", __knownByClassName);
}
}
return __known;
}
}
public static void setKnown(String... classes)
{
try (AutoLock l = __lock.lock())
{
if (!__known.isEmpty())
throw new IllegalStateException("Known configuration classes already set");
for (String c : classes)
{
try
{
Class extends Configuration> clazz = Loader.loadClass(c);
Configuration configuration = clazz.getConstructor().newInstance();
if (!configuration.isAvailable())
{
if (LOG.isDebugEnabled())
LOG.warn("Configuration unavailable: {}", configuration);
__unavailable.add(configuration);
continue;
}
__known.add(clazz.getConstructor().newInstance());
__knownByClassName.add(c);
}
catch (Exception e)
{
LOG.warn("Problem loading known class", e);
}
}
sort(__known);
if (LOG.isDebugEnabled())
{
for (Configuration c : __known)
{
LOG.debug("known {}", c);
}
LOG.debug("Known Configurations {}", __knownByClassName);
}
}
}
// Only used by tests.
static void cleanKnown()
{
try (AutoLock l = __lock.lock())
{
__known.clear();
__unavailable.clear();
}
}
/**
* Get/Set/Create the server default Configuration ClassList.
* Get the class list from: a Server bean; or the attribute (which can
* either be a ClassList instance or an String[] of class names); or a new instance
* with default configuration classes.
* This method also adds the obtained ClassList instance as a dependent bean
* on the server and clears the attribute
*
* @param server The server the default is for
* @return the server default ClassList instance of the configuration classes for this server.
* Changes to this list will change the server default instance.
*/
public static Configurations setServerDefault(Server server)
{
Configurations configurations = server.getBean(Configurations.class);
if (configurations != null)
return configurations;
configurations = getServerDefault(server);
server.addBean(configurations);
server.setAttribute(Configuration.ATTR, null);
return configurations;
}
/**
* Get/Create the server default Configuration ClassList.
* Get the class list from: a Server bean; or the attribute (which can
* either be a ClassList instance or an String[] of class names); or a new instance
* with default configuration classes.
*
* @param server The server the default is for
* @return A copy of the server default ClassList instance of the configuration classes for this server.
* Changes to the returned list will not change the server default.
*/
public static Configurations getServerDefault(Server server)
{
Configurations configurations = null;
if (server != null)
{
configurations = server.getBean(Configurations.class);
if (configurations != null)
configurations = new Configurations(configurations);
else
{
Object attr = server.getAttribute(Configuration.ATTR);
LOG.debug("{} attr({})= {}", server, Configuration.ATTR, attr);
if (attr instanceof Configurations)
configurations = new Configurations((Configurations)attr);
else if (attr instanceof String[])
configurations = new Configurations((String[])attr);
}
}
if (configurations == null)
{
configurations = new Configurations(Configurations.getKnown().stream()
.filter(c -> c.isEnabledByDefault())
.map(c -> c.getClass().getName())
.toArray(String[]::new));
}
if (LOG.isDebugEnabled())
LOG.debug("default configurations for {}: {}", server, configurations);
return configurations;
}
protected List _configurations = new ArrayList<>();
public Configurations()
{
}
protected static Configuration newConfiguration(String classname)
{
if (LOG.isDebugEnabled())
{
if (!__knownByClassName.contains(classname))
LOG.warn("Unknown configuration {}. Not declared for ServiceLoader!", classname);
}
try
{
@SuppressWarnings("unchecked")
Class clazz = Loader.loadClass(classname);
return clazz.getConstructor().newInstance();
}
catch (Throwable e)
{
throw new RuntimeException(e);
}
}
public Configurations(String... classes)
{
add(classes);
}
public Configurations(List classes)
{
add(classes.toArray(new String[classes.size()]));
}
public Configurations(Configurations classlist)
{
this(classlist._configurations.stream()
.map(c -> c.getClass().getName())
.toArray(String[]::new));
}
@Override
public boolean add(Configuration configuration)
{
return addConfiguration(configuration);
}
public void add(Configuration... configurations)
{
for (Configuration configuration : configurations)
{
addConfiguration(configuration);
}
}
public void add(@Name("configClass") String... configClass)
{
for (String name : configClass)
{
addConfiguration(newConfiguration(name));
}
}
public T get(Class extends T> configClass)
{
for (Configuration configuration : _configurations)
{
if (configClass.isAssignableFrom(configuration.getClass()))
return (T)configuration;
}
return null;
}
public List getConfigurations(Class extends T> configClass)
{
List list = new ArrayList<>();
for (Configuration configuration : _configurations)
{
if (configClass.isAssignableFrom(configuration.getClass()))
list.add((T)configuration);
}
return list;
}
public void clear()
{
_configurations.clear();
}
public void set(Configuration... configurations)
{
clear();
add(configurations);
}
public void set(@Name("configClass") String... configClass)
{
clear();
add(configClass);
}
public void remove(Configuration... configurations)
{
List names = Arrays.asList(configurations).stream().map(c -> c.getClass().getName()).collect(Collectors.toList());
for (ListIterator i = _configurations.listIterator(); i.hasNext(); )
{
Configuration configuration = i.next();
if (names.contains(configuration.getClass().getName()))
i.remove();
}
}
public void remove(Class extends Configuration>... configClass)
{
List names = Arrays.asList(configClass).stream().map(c -> c.getName()).collect(Collectors.toList());
for (ListIterator i = _configurations.listIterator(); i.hasNext(); )
{
Configuration configuration = i.next();
if (names.contains(configuration.getClass().getName()))
i.remove();
}
}
public void remove(@Name("configClass") String... configClass)
{
List names = Arrays.asList(configClass);
for (ListIterator i = _configurations.listIterator(); i.hasNext(); )
{
Configuration configuration = i.next();
if (names.contains(configuration.getClass().getName()))
i.remove();
}
}
public int size()
{
return _configurations.size();
}
public String[] toArray()
{
return _configurations.stream().map(c -> c.getClass().getName()).toArray(String[]::new);
}
public void sort()
{
sort(_configurations);
if (LOG.isDebugEnabled())
{
for (Configuration c : _configurations)
{
LOG.debug("sorted {}", c);
}
}
}
public static void sort(List configurations)
{
// Sort the configurations
Map byName = new HashMap<>();
Map> replacedBy = new HashMap<>();
TopologicalSort sort = new TopologicalSort<>();
for (Configuration c : configurations)
{
byName.put(c.getClass().getName(), c);
if (c.replaces() != null)
replacedBy.computeIfAbsent(c.replaces().getName(), key -> new ArrayList<>()).add(c);
}
for (Configuration c : configurations)
{
for (String b : c.getDependencies())
{
Configuration before = byName.get(b);
if (before != null)
sort.addBeforeAfter(before, c);
if (replacedBy.containsKey(b))
replacedBy.get(b).forEach(bc -> sort.addBeforeAfter(bc, c));
}
for (String a : c.getDependents())
{
Configuration after = byName.get(a);
if (after != null)
sort.addBeforeAfter(c, after);
if (replacedBy.containsKey(a))
replacedBy.get(a).forEach(ac -> sort.addBeforeAfter(c, ac));
}
}
sort.sort(configurations);
}
public List getConfigurations()
{
return Collections.unmodifiableList(_configurations);
}
@Override
public Configuration get(int index)
{
return _configurations.get(index);
}
@Override
public Iterator iterator()
{
return getConfigurations().iterator();
}
private boolean addConfiguration(Configuration configuration)
{
String name = configuration.getClass().getName();
// Is this configuration known?
if (LOG.isDebugEnabled())
{
if (!__knownByClassName.contains(name))
LOG.warn("Unknown configuration {}. Not declared for ServiceLoader!", name);
}
// Do we need to replace any existing configuration?
Class extends Configuration> replaces = configuration.replaces();
if (replaces != null)
{
for (ListIterator i = _configurations.listIterator(); i.hasNext(); )
{
Configuration c = i.next();
if (c.getClass().getName().equals(replaces.getName()) ||
c.replaces() != null && c.replaces().getName().equals(replaces.getName()))
{
i.remove();
break;
}
}
}
//check if any existing configurations replace the one we're adding
for (ListIterator i = _configurations.listIterator(); i.hasNext(); )
{
Configuration c = i.next();
Class extends Configuration> r = c.replaces();
if (r != null)
{
if (r.getName().equals(configuration.getClass().getName()))
return false; //skip the addition, a replacement is already present
}
if (c.getClass().getName().equals(configuration.getClass().getName()))
return false; //don't add same one twice
}
return _configurations.add(configuration);
}
@Override
public String toString()
{
return String.format("%s@%x", this.getClass(), this.hashCode());
}
public void preConfigure(WebAppContext webapp) throws Exception
{
// Configure webapp
// iterate with index to allows changes to the Configurations
// during calls to preConfiguration.
for (int i = 0; i < _configurations.size(); i++)
{
Configuration configuration = _configurations.get(i);
LOG.debug("preConfigure with {}", configuration);
configuration.preConfigure(webapp);
if (_configurations.get(i) != configuration)
throw new ConcurrentModificationException("Cannot change prior configuration");
}
}
/**
* @param webapp The webapp to configure
* @return false if a {@link Configuration#abort(WebAppContext)} returns true, true otherwise
* @throws Exception Thrown by {@link Configuration#configure(WebAppContext)}
*/
public boolean configure(WebAppContext webapp) throws Exception
{
// Configure webapp
for (Configuration configuration : _configurations)
{
LOG.debug("configure {}", configuration);
configuration.configure(webapp);
if (configuration.abort(webapp))
return false;
}
return true;
}
public void postConfigure(WebAppContext webapp) throws Exception
{
// Configure webapp
for (Configuration configuration : _configurations)
{
LOG.debug("postConfigure {}", configuration);
configuration.postConfigure(webapp);
}
}
@Override
public String dump()
{
return Dumpable.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
Dumpable.dumpObjects(out, indent, this,
new DumpableCollection("Known", Configurations.getKnown()),
new DumpableCollection("Unavailable", Configurations.__unavailable));
}
}