package org.glassfish.jersey.model.internal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.ws.rs.ConstrainedTo;
import javax.ws.rs.Priorities;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.annotation.Priority;
import org.glassfish.jersey.ExtendedConfig;
import org.glassfish.jersey.internal.LocalizationMessages;
import org.glassfish.jersey.internal.ServiceFinder;
import org.glassfish.jersey.internal.inject.Binder;
import org.glassfish.jersey.internal.inject.CompositeBinder;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.ProviderBinder;
import org.glassfish.jersey.internal.spi.AutoDiscoverable;
import org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.model.ContractProvider;
import org.glassfish.jersey.process.Inflector;
public class CommonConfig implements FeatureContext, ExtendedConfig {
private static final Logger LOGGER = Logger.getLogger(CommonConfig.class.getName());
private static final Function<Object, Binder> CAST_TO_BINDER = Binder.class::cast;
private final RuntimeType type;
private final Map<String, Object> properties;
private final Map<String, Object> immutablePropertiesView;
private final Collection<String> immutablePropertyNames;
private final ComponentBag componentBag;
private final List<FeatureRegistration> newFeatureRegistrations;
private final Set<Class<? extends Feature>> enabledFeatureClasses;
private final Set<Feature> enabledFeatures;
private boolean disableMetaProviderConfiguration;
private static final class FeatureRegistration {
private final Class<? extends Feature> featureClass;
private final Feature feature;
private final RuntimeType runtimeType;
private FeatureRegistration(final Class<? extends Feature> featureClass) {
this.featureClass = featureClass;
this.feature = null;
final ConstrainedTo runtimeTypeConstraint = featureClass.getAnnotation(ConstrainedTo.class);
this.runtimeType = runtimeTypeConstraint == null ? null : runtimeTypeConstraint.value();
}
private FeatureRegistration(final Feature feature) {
this.featureClass = feature.getClass();
this.feature = feature;
final ConstrainedTo runtimeTypeConstraint = featureClass.getAnnotation(ConstrainedTo.class);
this.runtimeType = runtimeTypeConstraint == null ? null : runtimeTypeConstraint.value();
}
private Class<? extends Feature> getFeatureClass() {
return featureClass;
}
private Feature getFeature() {
return feature;
}
private RuntimeType getFeatureRuntimeType() {
return runtimeType;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FeatureRegistration)) {
return false;
}
final FeatureRegistration other = (FeatureRegistration) obj;
return (featureClass == other.featureClass)
|| (feature != null && (feature == other.feature || feature.equals(other.feature)));
}
@Override
public int hashCode() {
int hash = 47;
hash = 13 * hash + (feature != null ? feature.hashCode() : 0);
hash = 13 * hash + (featureClass != null ? featureClass.hashCode() : 0);
return hash;
}
}
public CommonConfig(final RuntimeType type, final Predicate<ContractProvider> registrationStrategy) {
this.type = type;
this.properties = new HashMap<>();
this.immutablePropertiesView = Collections.unmodifiableMap(properties);
this.immutablePropertyNames = Collections.unmodifiableCollection(properties.keySet());
this.componentBag = ComponentBag.newInstance(registrationStrategy);
this.newFeatureRegistrations = new LinkedList<>();
this.enabledFeatureClasses = Collections.newSetFromMap(new IdentityHashMap<>());
this.enabledFeatures = new HashSet<>();
this.disableMetaProviderConfiguration = false;
}
public CommonConfig(final CommonConfig config) {
this.type = config.type;
this.properties = new HashMap<>(config.properties.size());
this.immutablePropertiesView = Collections.unmodifiableMap(this.properties);
this.immutablePropertyNames = Collections.unmodifiableCollection(this.properties.keySet());
this.componentBag = config.componentBag.copy();
this.newFeatureRegistrations = new LinkedList<>();
this.enabledFeatureClasses = Collections.newSetFromMap(new IdentityHashMap<>());
this.enabledFeatures = new HashSet<>();
copy(config, false);
}
private void copy(final CommonConfig config, final boolean loadComponentBag) {
this.properties.clear();
this.properties.putAll(config.properties);
this.newFeatureRegistrations.clear();
this.newFeatureRegistrations.addAll(config.newFeatureRegistrations);
this.enabledFeatureClasses.clear();
this.enabledFeatureClasses.addAll(config.enabledFeatureClasses);
this.enabledFeatures.clear();
this.enabledFeatures.addAll(config.enabledFeatures);
this.disableMetaProviderConfiguration = config.disableMetaProviderConfiguration;
if (loadComponentBag) {
this.componentBag.loadFrom(config.componentBag);
}
}
@Override
public ExtendedConfig getConfiguration() {
return this;
}
@Override
public RuntimeType getRuntimeType() {
return type;
}
@Override
public Map<String, Object> getProperties() {
return immutablePropertiesView;
}
@Override
public Object getProperty(final String name) {
return properties.get(name);
}
@Override
public boolean isProperty(final String name) {
return PropertiesHelper.isProperty(getProperty(name));
}
@Override
public Collection<String> getPropertyNames() {
return immutablePropertyNames;
}
@Override
public boolean isEnabled(final Class<? extends Feature> featureClass) {
return enabledFeatureClasses.contains(featureClass);
}
@Override
public boolean isEnabled(final Feature feature) {
return enabledFeatures.contains(feature);
}
@Override
public boolean isRegistered(final Object component) {
return componentBag.getInstances().contains(component);
}
@Override
public boolean isRegistered(final Class<?> componentClass) {
return componentBag.getRegistrations().contains(componentClass);
}
@Override
public Map<Class<?>, Integer> getContracts(final Class<?> componentClass) {
final ContractProvider model = componentBag.getModel(componentClass);
return (model == null) ? Collections.emptyMap() : model.getContractMap();
}
@Override
public Set<Class<?>> getClasses() {
return componentBag.getClasses();
}
@Override
public Set<Object> getInstances() {
return componentBag.getInstances();
}
public final ComponentBag getComponentBag() {
return componentBag;
}
protected Inflector<ContractProvider.Builder, ContractProvider> getModelEnhancer(final Class<?> componentClass) {
return ComponentBag.AS_IS;
}
public CommonConfig setProperties(final Map<String, ?> properties) {
this.properties.clear();
if (properties != null) {
this.properties.putAll(properties);
}
return this;
}
public CommonConfig addProperties(final Map<String, ?> properties) {
if (properties != null) {
this.properties.putAll(properties);
}
return this;
}
@Override
public CommonConfig property(final String name, final Object value) {
if (value == null) {
properties.remove(name);
} else {
properties.put(name, value);
}
return this;
}
@Override
public CommonConfig register(final Class<?> componentClass) {
checkComponentClassNotNull(componentClass);
if (componentBag.register(componentClass, getModelEnhancer(componentClass))) {
processFeatureRegistration(null, componentClass);
}
return this;
}
@Override
public CommonConfig register(final Class<?> componentClass, final int bindingPriority) {
checkComponentClassNotNull(componentClass);
if (componentBag.register(componentClass, bindingPriority, getModelEnhancer(componentClass))) {
processFeatureRegistration(null, componentClass);
}
return this;
}
@Override
public CommonConfig register(final Class<?> componentClass, final Class<?>... contracts) {
checkComponentClassNotNull(componentClass);
if (contracts == null || contracts.length == 0) {
LOGGER.warning(LocalizationMessages.COMPONENT_CONTRACTS_EMPTY_OR_NULL(componentClass));
return this;
}
if (componentBag.register(componentClass, asNewIdentitySet(contracts), getModelEnhancer(componentClass))) {
processFeatureRegistration(null, componentClass);
}
return this;
}
@Override
public CommonConfig register(final Class<?> componentClass, final Map<Class<?>, Integer> contracts) {
checkComponentClassNotNull(componentClass);
if (componentBag.register(componentClass, contracts, getModelEnhancer(componentClass))) {
processFeatureRegistration(null, componentClass);
}
return this;
}
@Override
public CommonConfig register(final Object component) {
checkProviderNotNull(component);
final Class<?> componentClass = component.getClass();
if (componentBag.register(component, getModelEnhancer(componentClass))) {
processFeatureRegistration(component, componentClass);
}
return this;
}
@Override
public CommonConfig register(final Object component, final int bindingPriority) {
checkProviderNotNull(component);
final Class<?> componentClass = component.getClass();
if (componentBag.register(component, bindingPriority, getModelEnhancer(componentClass))) {
processFeatureRegistration(component, componentClass);
}
return this;
}
@Override
public CommonConfig register(final Object component, final Class<?>... contracts) {
checkProviderNotNull(component);
final Class<?> componentClass = component.getClass();
if (contracts == null || contracts.length == 0) {
LOGGER.warning(LocalizationMessages.COMPONENT_CONTRACTS_EMPTY_OR_NULL(componentClass));
return this;
}
if (componentBag.register(component, asNewIdentitySet(contracts), getModelEnhancer(componentClass))) {
processFeatureRegistration(component, componentClass);
}
return this;
}
@Override
public CommonConfig register(final Object component, final Map<Class<?>, Integer> contracts) {
checkProviderNotNull(component);
final Class<?> componentClass = component.getClass();
if (componentBag.register(component, contracts, getModelEnhancer(componentClass))) {
processFeatureRegistration(component, componentClass);
}
return this;
}
private void processFeatureRegistration(final Object component, final Class<?> componentClass) {
final ContractProvider model = componentBag.getModel(componentClass);
if (model.getContracts().contains(Feature.class)) {
@SuppressWarnings("unchecked")
final FeatureRegistration registration = (component != null)
? new FeatureRegistration((Feature) component)
: new FeatureRegistration((Class<? extends Feature>) componentClass);
newFeatureRegistrations.add(registration);
}
}
public CommonConfig loadFrom(final Configuration config) {
if (config instanceof CommonConfig) {
final CommonConfig commonConfig = (CommonConfig) config;
copy(commonConfig, true);
this.disableMetaProviderConfiguration = !commonConfig.enabledFeatureClasses.isEmpty();
} else {
setProperties(config.getProperties());
this.enabledFeatures.clear();
this.enabledFeatureClasses.clear();
componentBag.clear();
resetRegistrations();
for (final Class<?> clazz : config.getClasses()) {
if (Feature.class.isAssignableFrom(clazz) && config.isEnabled((Class<? extends Feature>) clazz)) {
this.disableMetaProviderConfiguration = true;
}
register(clazz, config.getContracts(clazz));
}
for (final Object instance : config.getInstances()) {
if (instance instanceof Feature && config.isEnabled((Feature) instance)) {
this.disableMetaProviderConfiguration = true;
}
register(instance, config.getContracts(instance.getClass()));
}
}
return this;
}
private Set<Class<?>> asNewIdentitySet(final Class<?>... contracts) {
final Set<Class<?>> result = Collections.newSetFromMap(new IdentityHashMap<>());
result.addAll(Arrays.asList(contracts));
return result;
}
private void checkProviderNotNull(final Object provider) {
if (provider == null) {
throw new IllegalArgumentException(LocalizationMessages.COMPONENT_CANNOT_BE_NULL());
}
}
private void checkComponentClassNotNull(final Class<?> componentClass) {
if (componentClass == null) {
throw new IllegalArgumentException(LocalizationMessages.COMPONENT_CLASS_CANNOT_BE_NULL());
}
}
public void configureAutoDiscoverableProviders(final InjectionManager injectionManager,
final Collection<AutoDiscoverable> autoDiscoverables,
final boolean forcedOnly) {
if (!disableMetaProviderConfiguration) {
final Set<AutoDiscoverable> providers = new TreeSet<>((o1, o2) -> {
final int p1 = o1.getClass().isAnnotationPresent(Priority.class)
? o1.getClass().getAnnotation(Priority.class).value() : Priorities.USER;
final int p2 = o2.getClass().isAnnotationPresent(Priority.class)
? o2.getClass().getAnnotation(Priority.class).value() : Priorities.USER;
return (p1 < p2 || p1 == p2) ? -1 : 1;
});
final List<ForcedAutoDiscoverable> forcedAutoDiscroverables = new LinkedList<>();
for (Class<ForcedAutoDiscoverable> forcedADType : ServiceFinder.find(ForcedAutoDiscoverable.class, true)
.toClassArray()) {
forcedAutoDiscroverables.add(injectionManager.createAndInitialize(forcedADType));
}
providers.addAll(forcedAutoDiscroverables);
if (!forcedOnly) {
providers.addAll(autoDiscoverables);
}
for (final AutoDiscoverable autoDiscoverable : providers) {
final ConstrainedTo constrainedTo = autoDiscoverable.getClass().getAnnotation(ConstrainedTo.class);
if (constrainedTo == null || type.equals(constrainedTo.value())) {
try {
autoDiscoverable.configure(this);
} catch (final Exception e) {
LOGGER.log(Level.FINE,
LocalizationMessages.AUTODISCOVERABLE_CONFIGURATION_FAILED(autoDiscoverable.getClass()), e);
}
}
}
}
}
public void configureMetaProviders(InjectionManager injectionManager, ManagedObjectsFinalizer finalizer) {
final Set<Object> configuredExternals = Collections.newSetFromMap(new IdentityHashMap<>());
final Set<Binder> configuredBinders = configureBinders(injectionManager, Collections.emptySet());
if (!disableMetaProviderConfiguration) {
configureExternalObjects(injectionManager, configuredExternals);
configureFeatures(injectionManager, new HashSet<>(), resetRegistrations(), finalizer);
configureExternalObjects(injectionManager, configuredExternals);
configureBinders(injectionManager, configuredBinders);
}
}
private Set<Binder> configureBinders(InjectionManager injectionManager, Set<Binder> configured) {
Set<Binder> allConfigured = Collections.newSetFromMap(new IdentityHashMap<>());
allConfigured.addAll(configured);
Collection<Binder> binders = getBinder(configured);
if (!binders.isEmpty()) {
injectionManager.register(CompositeBinder.wrap(binders));
allConfigured.addAll(binders);
}
return allConfigured;
}
private Collection<Binder> getBinder(Set<Binder> configured) {
return componentBag.getInstances(ComponentBag.BINDERS_ONLY)
.stream()
.map(CAST_TO_BINDER)
.filter(binder -> !configured.contains(binder))
.collect(Collectors.toList());
}
private void configureExternalObjects(InjectionManager injectionManager, Set<Object> externalObjects) {
Consumer<Object> registerOnce = o -> {
if (!externalObjects.contains(o)) {
injectionManager.register(o);
externalObjects.add(o);
}
};
componentBag.getInstances(model -> ComponentBag.EXTERNAL_ONLY.test(model, injectionManager))
.forEach(registerOnce);
componentBag.getClasses(model -> ComponentBag.EXTERNAL_ONLY.test(model, injectionManager))
.forEach(registerOnce);
}
private void configureFeatures(InjectionManager injectionManager,
Set<FeatureRegistration> processed,
List<FeatureRegistration> unprocessed,
ManagedObjectsFinalizer managedObjectsFinalizer) {
FeatureContextWrapper featureContextWrapper = null;
for (final FeatureRegistration registration : unprocessed) {
if (processed.contains(registration)) {
LOGGER.config(LocalizationMessages.FEATURE_HAS_ALREADY_BEEN_PROCESSED(registration.getFeatureClass()));
continue;
}
final RuntimeType runtimeTypeConstraint = registration.getFeatureRuntimeType();
if (runtimeTypeConstraint != null && !type.equals(runtimeTypeConstraint)) {
LOGGER.config(LocalizationMessages.FEATURE_CONSTRAINED_TO_IGNORED(
registration.getFeatureClass(), registration.runtimeType, type));
continue;
}
Feature feature = registration.getFeature();
if (feature == null) {
feature = injectionManager.createAndInitialize(registration.getFeatureClass());
managedObjectsFinalizer.registerForPreDestroyCall(feature);
} else {
if (!RuntimeType.CLIENT.equals(type)) {
injectionManager.inject(feature);
}
}
if (enabledFeatures.contains(feature)) {
LOGGER.config(LocalizationMessages.FEATURE_HAS_ALREADY_BEEN_PROCESSED(feature));
continue;
}
if (featureContextWrapper == null) {
featureContextWrapper = new FeatureContextWrapper(this, injectionManager);
}
final boolean success = feature.configure(featureContextWrapper);
if (success) {
processed.add(registration);
final ContractProvider providerModel = componentBag.getModel(feature.getClass());
if (providerModel != null) {
ProviderBinder.bindProvider(feature, providerModel, injectionManager);
}
configureFeatures(injectionManager, processed, resetRegistrations(), managedObjectsFinalizer);
enabledFeatureClasses.add(registration.getFeatureClass());
enabledFeatures.add(feature);
}
}
}
private List<FeatureRegistration> resetRegistrations() {
final List<FeatureRegistration> result = new ArrayList<>(newFeatureRegistrations);
newFeatureRegistrations.clear();
return result;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CommonConfig)) {
return false;
}
final CommonConfig that = (CommonConfig) o;
if (type != that.type) {
return false;
}
if (!properties.equals(that.properties)) {
return false;
}
if (!componentBag.equals(that.componentBag)) {
return false;
}
if (!enabledFeatureClasses.equals(that.enabledFeatureClasses)) {
return false;
}
if (!enabledFeatures.equals(that.enabledFeatures)) {
return false;
}
if (!newFeatureRegistrations.equals(that.newFeatureRegistrations)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = type.hashCode();
result = 31 * result + properties.hashCode();
result = 31 * result + componentBag.hashCode();
result = 31 * result + newFeatureRegistrations.hashCode();
result = 31 * result + enabledFeatures.hashCode();
result = 31 * result + enabledFeatureClasses.hashCode();
return result;
}
}