org.eclipse.jetty.ee8.webapp.Configurations Maven / Gradle / Ivy
Show all versions of jetty-ee8-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.ee8.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()))
//skip the addition, a replacement is already present
return false;
}
if (c.getClass().getName().equals(configuration.getClass().getName()))
//don't add same one twice
return false;
}
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));
}
}