blob: eeac2e8c8412e5124b7785dea51c25c06f64e49c [file] [log] [blame]
/*
* Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.jersey.model.internal;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.ws.rs.NameBinding;
import javax.ws.rs.core.Feature;
import javax.annotation.Priority;
import javax.inject.Scope;
import org.glassfish.jersey.Severity;
import org.glassfish.jersey.internal.Errors;
import org.glassfish.jersey.internal.LocalizationMessages;
import org.glassfish.jersey.internal.inject.Binder;
import org.glassfish.jersey.internal.inject.Binding;
import org.glassfish.jersey.internal.inject.Bindings;
import org.glassfish.jersey.internal.inject.ClassBinding;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.InstanceBinding;
import org.glassfish.jersey.internal.inject.Providers;
import org.glassfish.jersey.model.ContractProvider;
import org.glassfish.jersey.process.Inflector;
import org.glassfish.jersey.spi.ExecutorServiceProvider;
import org.glassfish.jersey.spi.ScheduledExecutorServiceProvider;
/**
* An internal Jersey container for custom component classes and instances.
* <p/>
* The component bag can automatically compute a {@link ContractProvider contract provider} model
* for the registered component type and stores it with the component registration.
* <p>
* The rules for managing components inside a component bag are derived from the
* rules of JAX-RS {@link javax.ws.rs.core.Configurable} API. In short:
* <ul>
* <li>The iteration order of registered components mirrors the registration order
* of these components.</li>
* <li>There can be only one registration for any given component type.</li>
* <li>Existing registrations cannot be overridden (any attempt to override
* an existing registration will be rejected).</li>
* </ul>
* </p>
*
* @author Marek Potociar
*/
public class ComponentBag {
/**
* A filtering strategy that excludes all pure meta-provider models (i.e. models that only contain
* recognized meta-provider contracts - {@link javax.ws.rs.core.Feature} and/or {@link Binder} and/or external meta-provider
* from {@link org.glassfish.jersey.internal.inject.InjectionManager#isRegistrable(Class)}).
* <p>
* This filter predicate returns {@code false} for all {@link org.glassfish.jersey.model.ContractProvider contract provider models}
* that represent a model containing only recognized meta-provider contracts.
* </p>
*/
private static final Predicate<ContractProvider> EXCLUDE_META_PROVIDERS = model -> {
final Set<Class<?>> contracts = model.getContracts();
if (contracts.isEmpty()) {
return true;
}
byte count = 0;
if (contracts.contains(Feature.class)) {
count++;
}
if (contracts.contains(Binder.class)) {
count++;
}
return contracts.size() > count;
};
private static final Function<Object, Binder> CAST_TO_BINDER = Binder.class::cast;
/**
* A method creates the {@link Predicate} which is able to filter all Jersey meta-providers along with the components which
* is able to register the current used {@link InjectionManager}.
*
* @param injectionManager current injection manager.
* @return {@code Predicate} excluding Jersey meta-providers and the specific ones for a current {@code InjectionManager}.
*/
public static Predicate<ContractProvider> excludeMetaProviders(InjectionManager injectionManager) {
return EXCLUDE_META_PROVIDERS.and(model -> !injectionManager.isRegistrable(model.getImplementationClass()));
}
/**
* A filtering strategy that includes only models that contain contract registrable by
* {@link InjectionManager}.
* <p>
* This filter predicate returns {@code true} for all {@link org.glassfish.jersey.model.ContractProvider contract provider models}
* that represent an object which can be registered using specific {@link InjectionManager}
* contract.
* </p>
*/
public static final BiPredicate<ContractProvider, InjectionManager> EXTERNAL_ONLY = (model, injectionManager) ->
model.getImplementationClass() != null && injectionManager.isRegistrable(model.getImplementationClass());
/**
* A filtering strategy that includes only models that contain {@link Binder} provider contract.
* <p>
* This filter predicate returns {@code true} for all {@link org.glassfish.jersey.model.ContractProvider contract provider models}
* that represent a provider registered to provide {@link Binder} contract.
* </p>
*/
public static final Predicate<ContractProvider> BINDERS_ONLY = model -> model.getContracts().contains(Binder.class);
/**
* A filtering strategy that includes only models that contain {@link ExecutorServiceProvider} provider contract.
* <p>
* This filter predicate returns {@code true} for all {@link org.glassfish.jersey.model.ContractProvider contract provider models}
* that represent a provider registered to provide {@link ExecutorServiceProvider} contract.
* </p>
*/
public static final Predicate<ContractProvider> EXECUTOR_SERVICE_PROVIDER_ONLY =
model -> model.getContracts().contains(ExecutorServiceProvider.class)
&& !model.getContracts().contains(ScheduledExecutorServiceProvider.class);
/**
* A filtering strategy that includes only models that contain {@link ScheduledExecutorServiceProvider} provider contract.
* <p>
* This filter predicate returns {@code true} for all {@link org.glassfish.jersey.model.ContractProvider contract provider models}
* that represent a provider registered to provide {@link ScheduledExecutorServiceProvider} contract.
* </p>
*/
public static final Predicate<ContractProvider> SCHEDULED_EXECUTOR_SERVICE_PROVIDER_ONLY =
model -> model.getContracts().contains(ScheduledExecutorServiceProvider.class);
/**
* A filtering strategy that excludes models with no recognized contracts.
* <p>
* This filter predicate returns {@code false} for all {@link org.glassfish.jersey.model.ContractProvider contract provider models}
* that are empty, i.e. do not contain any recognized contracts.
* </p>
*/
public static final Predicate<ContractProvider> EXCLUDE_EMPTY = model -> !model.getContracts().isEmpty();
/**
* A filtering strategy that accepts any contract provider model.
* <p>
* This filter predicate returns {@code true} for any contract provider model.
* </p>
*/
public static final Predicate<ContractProvider> INCLUDE_ALL = contractProvider -> true;
/**
* Contract provider model enhancer that builds a model as is, without any
* modifications.
*/
static final Inflector<ContractProvider.Builder, ContractProvider> AS_IS = ContractProvider.Builder::build;
/**
* Contract provider model registration strategy.
*/
private final Predicate<ContractProvider> registrationStrategy;
/**
* Registered component classes collection and it's immutable view.
*/
private final Set<Class<?>> classes;
private final Set<Class<?>> classesView;
/**
* Registered component instances collection and it's immutable view.
*/
private final Set<Object> instances;
private final Set<Object> instancesView;
/**
* Map of contract provider models for the registered component classes and instances
* it's immutable view.
*/
private final Map<Class<?>, ContractProvider> models;
private final Set<Class<?>> modelKeysView;
/**
* Create new empty component bag.
*
* @param registrationStrategy function driving the decision (based on the introspected
* {@link org.glassfish.jersey.model.ContractProvider contract provider model}) whether
* or not should the component class registration continue
* towards a successful completion.
* @return a new empty component bag.
*/
public static ComponentBag newInstance(Predicate<ContractProvider> registrationStrategy) {
return new ComponentBag(registrationStrategy);
}
/**
* If {@code T} object is registered in {@link ComponentBag} using the {@link Binder}, {@code T} is not visible using the
* methods for getting classes and instances {@link ComponentBag#getClasses(Predicate)} and
* {@link ComponentBag#getInstances(Predicate)}.
* <p>
* Method selects all {@link org.glassfish.jersey.internal.inject.Binding bindings} and picks up the instances or creates
* the instances from {@link ClassBinding} (injection does not work at this moment).
*
* @param injectionManager injection manager to create an object from {@code T} class.
* @param componentBag component bag which provides registered binders.
* @return all instances/classes registered using binders.
*/
@SuppressWarnings("unchecked")
public static <T> List<T> getFromBinders(InjectionManager injectionManager, ComponentBag componentBag,
Function<Object, T> cast, Predicate<Binding> filter) {
Function<Binding, Object> bindingToObject = binding -> {
if (binding instanceof ClassBinding) {
ClassBinding classBinding = (ClassBinding) binding;
return injectionManager.createAndInitialize(classBinding.getService());
} else {
InstanceBinding instanceBinding = (InstanceBinding) binding;
return instanceBinding.getService();
}
};
return componentBag.getInstances(ComponentBag.BINDERS_ONLY).stream()
.map(CAST_TO_BINDER)
.flatMap(binder -> Bindings.getBindings(injectionManager, binder).stream())
.filter(filter)
.map(bindingToObject)
.map(cast)
.collect(Collectors.toList());
}
private ComponentBag(Predicate<ContractProvider> registrationStrategy) {
this.registrationStrategy = registrationStrategy;
this.classes = new LinkedHashSet<>();
this.instances = new LinkedHashSet<>();
this.models = new IdentityHashMap<>();
this.classesView = Collections.unmodifiableSet(classes);
this.instancesView = Collections.unmodifiableSet(instances);
this.modelKeysView = Collections.unmodifiableSet(models.keySet());
}
private ComponentBag(Predicate<ContractProvider> registrationStrategy,
Set<Class<?>> classes,
Set<Object> instances,
Map<Class<?>, ContractProvider> models) {
this.registrationStrategy = registrationStrategy;
this.classes = classes;
this.instances = instances;
this.models = models;
this.classesView = Collections.unmodifiableSet(classes);
this.instancesView = Collections.unmodifiableSet(instances);
this.modelKeysView = Collections.unmodifiableSet(models.keySet());
}
/**
* Register a component class using a given registration strategy.
*
* @param componentClass class to be introspected as a contract provider and registered, based
* on the registration strategy decision.
* @param modelEnhancer custom contract provider model enhancer.
* @return {@code true} if the component registration was successful.
*/
public boolean register(Class<?> componentClass, Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
final boolean result = registerModel(componentClass, ContractProvider.NO_PRIORITY, null, modelEnhancer);
if (result) {
classes.add(componentClass);
}
return result;
}
/**
* Register a component class as a contract provider with an explicitly specified binding priority.
*
* @param componentClass class to be introspected as a contract provider and registered.
* @param priority explicitly specified binding priority for the provider contracts implemented
* by the component.
* @param modelEnhancer custom contract provider model enhancer.
* @return {@code true} if the component registration was successful.
*/
public boolean register(Class<?> componentClass,
int priority,
Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
final boolean result = registerModel(componentClass, priority, null, modelEnhancer);
if (result) {
classes.add(componentClass);
}
return result;
}
/**
* Register a component class as a contract provider for the specified contracts.
*
* @param componentClass class to be introspected as a contract provider and registered.
* @param contracts contracts to bind the component class to.
* @param modelEnhancer custom contract provider model enhancer.
* @return {@code true} if the component registration was successful.
*/
public boolean register(Class<?> componentClass,
Set<Class<?>> contracts,
Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
final boolean result =
registerModel(componentClass, ContractProvider.NO_PRIORITY, asMap(contracts), modelEnhancer);
if (result) {
classes.add(componentClass);
}
return result;
}
/**
* Register a component class as a contract provider for the specified contracts.
*
* @param componentClass class to be introspected as a contract provider and registered.
* @param contracts contracts with their priorities to bind the component class to.
* @param modelEnhancer custom contract provider model enhancer.
* @return {@code true} if the component registration was successful.
*/
public boolean register(Class<?> componentClass,
Map<Class<?>, Integer> contracts,
Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
final boolean result =
registerModel(componentClass, ContractProvider.NO_PRIORITY, contracts, modelEnhancer);
if (result) {
classes.add(componentClass);
}
return result;
}
/**
* Register a component using a given registration strategy.
*
* @param component instance to be introspected as a contract provider and registered, based
* on the registration strategy decision.
* @param modelEnhancer custom contract provider model enhancer.
* @return {@code true} if the component registration was successful.
*/
public boolean register(Object component, Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
final Class<?> componentClass = component.getClass();
final boolean result = registerModel(componentClass, ContractProvider.NO_PRIORITY, null, modelEnhancer);
if (result) {
instances.add(component);
}
return result;
}
/**
* Register a component as a contract provider with an explicitly specified binding priority.
*
* @param component instance to be introspected as a contract provider and registered, based
* on the registration strategy decision.
* @param priority explicitly specified binding priority for the provider contracts implemented
* by the component.
* @param modelEnhancer custom contract provider model enhancer.
* @return {@code true} if the component registration was successful.
*/
public boolean register(Object component,
int priority,
Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
final Class<?> componentClass = component.getClass();
final boolean result = registerModel(componentClass, priority, null, modelEnhancer);
if (result) {
instances.add(component);
}
return result;
}
/**
* Register a component as a contract provider for the specified contracts.
*
* @param component instance to be introspected as a contract provider and registered, based
* on the registration strategy decision.
* @param contracts contracts to bind the component to.
* @param modelEnhancer custom contract provider model enhancer.
* @return {@code true} if the component registration was successful.
*/
public boolean register(Object component,
Set<Class<?>> contracts,
Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
final Class<?> componentClass = component.getClass();
final boolean result =
registerModel(componentClass, ContractProvider.NO_PRIORITY, asMap(contracts), modelEnhancer);
if (result) {
instances.add(component);
}
return result;
}
/**
* Register a component as a contract provider for the specified contracts.
*
* @param component instance to be introspected as a contract provider and registered, based
* on the registration strategy decision.
* @param contracts contracts with their priorities to bind the component to.
* @param modelEnhancer custom contract provider model enhancer.
* @return {@code true} if the component registration was successful.
*/
public boolean register(Object component,
Map<Class<?>, Integer> contracts,
Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
final Class<?> componentClass = component.getClass();
final boolean result =
registerModel(componentClass, ContractProvider.NO_PRIORITY, contracts, modelEnhancer);
if (result) {
instances.add(component);
}
return result;
}
/**
* Register a {@link ContractProvider contract provider model} for a given class.
*
* @param componentClass registered component class.
* @param defaultPriority default component priority. If {@value ContractProvider#NO_PRIORITY},
* the value from the component class {@link javax.annotation.Priority} annotation will be used
* (if any).
* @param contractMap map of contracts and their binding priorities. If {@code null}, the contracts will
* gathered by introspecting the component class. Content of the contract map
* is not modified during the registration processing.
* @param modelEnhancer custom contract provider model enhancer.
* @return {@code true} upon successful registration of a contract provider model for a given component class,
* {@code false} otherwise.
*/
private boolean registerModel(final Class<?> componentClass,
final int defaultPriority,
final Map<Class<?>, Integer> contractMap,
final Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
return Errors.process(() -> {
if (models.containsKey(componentClass)) {
Errors.error(LocalizationMessages.COMPONENT_TYPE_ALREADY_REGISTERED(componentClass),
Severity.HINT);
return false;
}
// Register contracts
final ContractProvider model = modelFor(componentClass, defaultPriority, contractMap, modelEnhancer);
// Apply registration strategy
if (!registrationStrategy.test(model)) {
return false;
}
models.put(componentClass, model);
return true;
});
}
/**
* Create a contract provider model by introspecting a component class.
*
* @param componentClass component class to create contract provider model for.
* @return contract provider model for the class.
*/
public static ContractProvider modelFor(final Class<?> componentClass) {
return modelFor(componentClass, ContractProvider.NO_PRIORITY, null, AS_IS);
}
/**
* Create a contract provider for a given component class.
*
* @param componentClass component class to create contract provider model for.
* @param defaultPriority default component priority. If {@value ContractProvider#NO_PRIORITY},
* the value from the component class {@link javax.annotation.Priority} annotation will be used
* (if any).
* @param contractMap map of contracts and their binding priorities. If {@code null}, the contracts will
* gathered by introspecting the component class. Content of the contract map
* is not modified during the registration processing.
* @param modelEnhancer custom contract provider model enhancer.
* @return contract provider model for the class.
*/
private static ContractProvider modelFor(final Class<?> componentClass,
final int defaultPriority,
final Map<Class<?>, Integer> contractMap,
final Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
Map<Class<?>, Integer> contracts;
if (contractMap == null) { // introspect
contracts = asMap(Providers.getProviderContracts(componentClass));
} else { // filter custom contracts
contracts = new HashMap<>(contractMap);
final Iterator<Class<?>> it = contracts.keySet().iterator();
while (it.hasNext()) {
final Class<?> contract = it.next();
if (contract == null) {
it.remove();
continue;
}
boolean failed = false;
if (!Providers.isSupportedContract(contract)) {
Errors.error(LocalizationMessages.CONTRACT_NOT_SUPPORTED(contract, componentClass),
Severity.WARNING);
failed = true;
}
if (!contract.isAssignableFrom(componentClass)) {
Errors.error(LocalizationMessages.CONTRACT_NOT_ASSIGNABLE(contract, componentClass),
Severity.WARNING);
failed = true;
}
if (failed) {
it.remove();
}
}
}
final ContractProvider.Builder builder = ContractProvider.builder(componentClass)
.addContracts(contracts)
.defaultPriority(defaultPriority);
// Process annotations (priority, name bindings, scope)
final boolean useAnnotationPriority = defaultPriority == ContractProvider.NO_PRIORITY;
for (Annotation annotation : componentClass.getAnnotations()) {
if (annotation instanceof Priority) {
if (useAnnotationPriority) {
builder.defaultPriority(((Priority) annotation).value());
}
} else {
for (Annotation metaAnnotation : annotation.annotationType().getAnnotations()) {
if (metaAnnotation instanceof NameBinding) {
builder.addNameBinding(annotation.annotationType());
}
if (metaAnnotation instanceof Scope) {
builder.scope(annotation.annotationType());
}
}
}
}
return modelEnhancer.apply(builder);
}
private static Map<Class<?>, Integer> asMap(Set<Class<?>> contractSet) {
Map<Class<?>, Integer> contracts = new IdentityHashMap<>();
for (Class<?> contract : contractSet) {
contracts.put(contract, ContractProvider.NO_PRIORITY);
}
return contracts;
}
/**
* Get all registered component classes, including {@link javax.ws.rs.core.Feature features}
* and {@link Binder binders} meta-providers.
*
* @return all registered component classes.
*/
public Set<Class<?>> getClasses() {
return classesView;
}
/**
* Get all registered component instances, including {@link javax.ws.rs.core.Feature features}
* and {@link Binder binders} meta-providers.
*
* @return all registered component instances.
*/
public Set<Object> getInstances() {
return instancesView;
}
/**
* Get a subset of all registered component classes using the {@code filter} predicate
* to determine for each component class based on it's contract provider class model whether
* it should be kept or filtered out.
*
* @param filter function that decides whether a particular class should be returned
* or not.
* @return filtered subset of registered component classes.
*/
public Set<Class<?>> getClasses(final Predicate<ContractProvider> filter) {
return classesView.stream()
.filter(input -> {
final ContractProvider model = getModel(input);
return filter.test(model);
})
.collect(Collectors.toCollection(LinkedHashSet::new));
}
/**
* Get a subset of all registered component instances using the {@code filter} predicate
* to determine for each component instance based on it's contract provider class model whether
* it should be kept or filtered out.
*
* @param filter function that decides whether a particular class should be returned
* or not.
* @return filtered subset of registered component instances.
*/
public Set<Object> getInstances(final Predicate<ContractProvider> filter) {
return instancesView.stream()
.filter(input -> {
final ContractProvider model = getModel(input.getClass());
return filter.test(model);
})
.collect(Collectors.toCollection(LinkedHashSet::new));
}
/**
* Get an unmodifiable view of all component classes, for which a registration exists
* (either class or instance based) in the component bag.
*
* @return set of classes of all component classes and instances registered in this
* component bag.
*/
public Set<Class<?>> getRegistrations() {
return modelKeysView;
}
/**
* Get a model for a given component class, or {@code null} if no such component is registered
* in the component bag.
*
* @param componentClass class of the registered component to retrieve the
* contract provider model for.
* @return model for a given component class, or {@code null} if no such component is registered.
*/
public ContractProvider getModel(Class<?> componentClass) {
return models.get(componentClass);
}
/**
* Get a copy of this component bag.
*
* @return component bag copy.
*/
public ComponentBag copy() {
return new ComponentBag(
registrationStrategy,
new LinkedHashSet<>(classes),
new LinkedHashSet<>(instances),
new IdentityHashMap<>(models));
}
/**
* Get immutable copy of a component bag.
*
* @return immutable view of a component bag.
*/
public ComponentBag immutableCopy() {
return new ImmutableComponentBag(this);
}
/**
* Removes all the component registrations and resets the component bag instance to
* a state as if it was create anew.
*/
public void clear() {
this.classes.clear();
this.instances.clear();
this.models.clear();
}
/**
* Clear and initialize the component registrations from given bag instance.
*
* @param bag component bag to initialize this one with.
*/
void loadFrom(final ComponentBag bag) {
clear();
this.classes.addAll(bag.classes);
this.instances.addAll(bag.instances);
this.models.putAll(bag.models);
}
/**
* Immutable version of {@link org.glassfish.jersey.model.internal.ComponentBag}.
*
* @author Marek Potociar
*/
private static class ImmutableComponentBag extends ComponentBag {
ImmutableComponentBag(ComponentBag original) {
super(original.registrationStrategy,
new LinkedHashSet<>(original.classes),
new LinkedHashSet<>(original.instances),
new IdentityHashMap<>(original.models));
}
@Override
public boolean register(Class<?> componentClass, Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
throw new IllegalStateException("This instance is read-only.");
}
@Override
public boolean register(Class<?> componentClass,
int priority,
Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
throw new IllegalStateException("This instance is read-only.");
}
@Override
public boolean register(Class<?> componentClass,
Set<Class<?>> contracts,
Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
throw new IllegalStateException("This instance is read-only.");
}
@Override
public boolean register(Class<?> componentClass,
Map<Class<?>, Integer> contracts,
Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
throw new IllegalStateException("This instance is read-only.");
}
@Override
public boolean register(Object component, Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
throw new IllegalStateException("This instance is read-only.");
}
@Override
public boolean register(Object component,
int priority,
Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
throw new IllegalStateException("This instance is read-only.");
}
@Override
public boolean register(Object component,
Set<Class<?>> contracts,
Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
throw new IllegalStateException("This instance is read-only.");
}
@Override
public boolean register(Object component,
Map<Class<?>, Integer> contracts,
Inflector<ContractProvider.Builder, ContractProvider> modelEnhancer) {
throw new IllegalStateException("This instance is read-only.");
}
@Override
public ComponentBag copy() {
// we're immutable => no need to copy
return this;
}
@Override
public ComponentBag immutableCopy() {
// we're immutable => no need to copy
return this;
}
@Override
public void clear() {
throw new IllegalStateException("This instance is read-only.");
}
}
}