| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sling.models.impl; |
| |
| import java.lang.ref.PhantomReference; |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.SoftReference; |
| import java.lang.reflect.*; |
| import java.util.*; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| |
| import javax.annotation.PostConstruct; |
| import javax.servlet.ServletRequest; |
| import javax.servlet.ServletRequestEvent; |
| import javax.servlet.ServletRequestListener; |
| import javax.servlet.ServletRequestWrapper; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.sling.api.SlingHttpServletRequest; |
| import org.apache.sling.api.adapter.Adaptable; |
| import org.apache.sling.api.adapter.AdapterFactory; |
| import org.apache.sling.api.adapter.AdapterManager; |
| import org.apache.sling.api.resource.Resource; |
| import org.apache.sling.commons.osgi.RankedServices; |
| import org.apache.sling.models.annotations.Model; |
| import org.apache.sling.models.annotations.ValidationStrategy; |
| import org.apache.sling.models.annotations.ViaProviderType; |
| import org.apache.sling.models.annotations.via.BeanProperty; |
| import org.apache.sling.models.export.spi.ModelExporter; |
| import org.apache.sling.models.factory.ExportException; |
| import org.apache.sling.models.factory.InvalidAdaptableException; |
| import org.apache.sling.models.factory.InvalidModelException; |
| import org.apache.sling.models.factory.MissingElementException; |
| import org.apache.sling.models.factory.MissingElementsException; |
| import org.apache.sling.models.factory.MissingExporterException; |
| import org.apache.sling.models.factory.ModelClassException; |
| import org.apache.sling.models.factory.ModelFactory; |
| import org.apache.sling.models.factory.PostConstructException; |
| import org.apache.sling.models.factory.ValidationException; |
| import org.apache.sling.models.impl.injectors.OSGiServiceInjector; |
| import org.apache.sling.models.impl.model.ConstructorParameter; |
| import org.apache.sling.models.impl.model.InjectableElement; |
| import org.apache.sling.models.impl.model.InjectableField; |
| import org.apache.sling.models.impl.model.InjectableMethod; |
| import org.apache.sling.models.impl.model.ModelClass; |
| import org.apache.sling.models.impl.model.ModelClassConstructor; |
| import org.apache.sling.models.impl.model.OptionalTypedInjectableElement; |
| import org.apache.sling.models.spi.AcceptsNullName; |
| import org.apache.sling.models.spi.DisposalCallback; |
| import org.apache.sling.models.spi.DisposalCallbackRegistry; |
| import org.apache.sling.models.spi.ImplementationPicker; |
| import org.apache.sling.models.spi.Injector; |
| import org.apache.sling.models.spi.ModelValidation; |
| import org.apache.sling.models.spi.ValuePreparer; |
| import org.apache.sling.models.spi.ViaProvider; |
| import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor; |
| import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory; |
| import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory2; |
| import org.apache.sling.models.spi.injectorspecific.StaticInjectAnnotationProcessorFactory; |
| import org.apache.sling.scripting.api.BindingsValuesProvidersByContext; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.FrameworkUtil; |
| import org.osgi.framework.ServiceRegistration; |
| import org.osgi.service.component.ComponentContext; |
| import org.osgi.service.component.annotations.Activate; |
| import org.osgi.service.component.annotations.Component; |
| import org.osgi.service.component.annotations.Deactivate; |
| import org.osgi.service.component.annotations.Reference; |
| import org.osgi.service.component.annotations.ReferenceCardinality; |
| import org.osgi.service.component.annotations.ReferencePolicy; |
| import org.osgi.service.component.annotations.ReferencePolicyOption; |
| import org.osgi.service.http.whiteboard.HttpWhiteboardConstants; |
| import org.osgi.service.metatype.annotations.Designate; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| @Component(immediate = true, service={ ModelFactory.class, ServletRequestListener.class }, |
| property= { HttpWhiteboardConstants.HTTP_WHITEBOARD_LISTENER+"=true", |
| HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT+"=(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "=*)" }) |
| @Designate(ocd=ModelAdapterFactoryConfiguration.class) |
| @SuppressWarnings("deprecation") |
| public class ModelAdapterFactory implements AdapterFactory, Runnable, ModelFactory, ServletRequestListener { |
| |
| // hard code this value since we always know exactly how many there are |
| private static final int VALUE_PREPARERS_COUNT = 2; |
| |
| private static final String REQUEST_MARKER_ATTRIBUTE = ModelAdapterFactory.class.getName() + ".RealRequest"; |
| |
| private static final Object REQUEST_MARKER_VALUE = new Object(); |
| |
| private static final String REQUEST_CACHE_ATTRIBUTE = ModelAdapterFactory.class.getName() + ".AdapterCache"; |
| |
| private static class DisposalCallbackRegistryImpl implements DisposalCallbackRegistry, Disposable { |
| |
| private List<DisposalCallback> callbacks = new ArrayList<>(); |
| |
| @Override |
| public void addDisposalCallback(@NotNull DisposalCallback callback) { |
| callbacks.add(callback); |
| } |
| |
| private void seal() { |
| callbacks = Collections.unmodifiableList(callbacks); |
| } |
| |
| |
| @Override |
| public void onDisposed() { |
| for (DisposalCallback callback : callbacks) { |
| callback.onDisposed(); |
| } |
| } |
| |
| } |
| |
| private static class CombinedDisposable implements Disposable { |
| private final Collection<Disposable> delegates = Collections.synchronizedCollection(new HashSet<Disposable>()); |
| |
| private void add(Disposable disposable) { |
| delegates.add(disposable); |
| } |
| |
| @Override |
| public void onDisposed() { |
| for (Disposable delegate : delegates) { |
| delegate.onDisposed(); |
| } |
| } |
| } |
| |
| private interface Disposable { |
| void onDisposed(); |
| } |
| |
| private static class RequestDisposalCallbacks { |
| private ConcurrentHashMap<ServletRequest, Disposable> callbacks = new ConcurrentHashMap<>(); |
| |
| public Collection<Disposable> values() { |
| return callbacks.values(); |
| } |
| |
| public Disposable remove(ServletRequest request) { |
| return callbacks.remove(request); |
| } |
| |
| public void put(ServletRequest request, Disposable registry) { |
| synchronized (callbacks) { |
| CombinedDisposable combinedDisposable = null; |
| Disposable current = callbacks.get(request); |
| if (current == null) { |
| callbacks.put(request, registry); |
| return; |
| } else if (current instanceof CombinedDisposable) { |
| combinedDisposable = (CombinedDisposable) current; |
| } else { |
| combinedDisposable = new CombinedDisposable(); |
| combinedDisposable.add(current); |
| callbacks.put(request, combinedDisposable); |
| } |
| combinedDisposable.add(registry); |
| } |
| } |
| } |
| |
| private ReferenceQueue<Object> queue; |
| |
| private ConcurrentMap<java.lang.ref.Reference<Object>, Disposable> disposalCallbacks; |
| |
| private RequestDisposalCallbacks requestDisposalCallbacks; |
| |
| @Override |
| public void run() { |
| clearDisposalCallbackRegistryQueue(); |
| } |
| |
| private void clearDisposalCallbackRegistryQueue() { |
| java.lang.ref.Reference<?> ref = queue.poll(); |
| while (ref != null) { |
| log.debug("calling disposal for {}.", ref); |
| Disposable registry = disposalCallbacks.remove(ref); |
| registry.onDisposed(); |
| ref = queue.poll(); |
| } |
| } |
| |
| private static final Logger log = LoggerFactory.getLogger(ModelAdapterFactory.class); |
| private static final Logger perfLog = LoggerFactory.getLogger(ModelAdapterFactory.class.getName() + "-perf"); |
| |
| private final ConcurrentMap<String, RankedServices<Injector>> injectors = new ConcurrentHashMap<>(); |
| private final RankedServices<Injector> sortedInjectors = new RankedServices<>(); |
| private final ConcurrentMap<Class<? extends ViaProviderType>, ViaProvider> viaProviders = new ConcurrentHashMap<>(); |
| |
| @Reference(name="injectAnnotationProcessorFactory", cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) |
| volatile Collection<InjectAnnotationProcessorFactory> injectAnnotationProcessorFactories; // this must be non-final for fieldOption=replace! |
| |
| @Reference(name="injectAnnotationProcessorFactory2", cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) |
| volatile Collection<InjectAnnotationProcessorFactory2> injectAnnotationProcessorFactories2; // this must be non-final for fieldOption=replace! |
| |
| private final RankedServices<StaticInjectAnnotationProcessorFactory> staticInjectAnnotationProcessorFactories = new RankedServices<>(); |
| |
| private final RankedServices<ImplementationPicker> implementationPickers = new RankedServices<>(); |
| |
| // bind the service with the highest priority (if a new one comes in this service gets restarted) |
| @Reference(cardinality=ReferenceCardinality.OPTIONAL, policyOption=ReferencePolicyOption.GREEDY) |
| private ModelValidation modelValidation = null; |
| |
| @Reference(name = "modelExporter", cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) |
| volatile Collection<ModelExporter> modelExporters; // this must be non-final for fieldOption=replace! |
| |
| @Reference |
| BindingsValuesProvidersByContext bindingsValuesProvidersByContext; |
| |
| @Reference |
| AdapterManager adapterManager; |
| |
| ModelPackageBundleListener listener; |
| |
| final AdapterImplementations adapterImplementations = new AdapterImplementations(); |
| |
| private ServiceRegistration jobRegistration; |
| |
| private ServiceRegistration configPrinterRegistration; |
| |
| // Use threadlocal to count recursive invocations and break recursing if a max. limit is reached (to avoid cyclic dependencies) |
| private ThreadLocal<ThreadInvocationCounter> invocationCountThreadLocal; |
| |
| private Map<Object, Map<Class<?>, SoftReference<Object>>> adapterCache; |
| |
| private SlingModelsScriptEngineFactory scriptEngineFactory; |
| |
| @Override |
| @SuppressWarnings("null") |
| public <AdapterType> AdapterType getAdapter(Object adaptable, Class<AdapterType> type) { |
| Result<AdapterType> result = internalCreateModelMeasured(adaptable, type); |
| if (!result.wasSuccessful()) { |
| if (result == Result.POST_CONSTRUCT_PREVENTED_MODEL_CONSTRUCTION) { |
| log.debug("Could not adapt to model as PostConstruct method returned false"); // do no construct runtime exception in this case |
| } else { |
| log.warn("Could not adapt to model", result.getThrowable()); |
| } |
| return null; |
| } else { |
| return result.getValue(); |
| } |
| } |
| |
| @Override |
| public @NotNull <ModelType> ModelType createModel(@NotNull Object adaptable, @NotNull Class<ModelType> type) throws MissingElementsException, |
| InvalidAdaptableException, ValidationException, InvalidModelException { |
| Result<ModelType> result = internalCreateModelMeasured(adaptable, type); |
| if (!result.wasSuccessful()) { |
| throw result.getThrowable(); |
| } |
| return result.getValue(); |
| } |
| |
| @Override |
| public boolean canCreateFromAdaptable(@NotNull Object adaptable, @NotNull Class<?> modelClass) throws ModelClassException { |
| return internalCanCreateFromAdaptable(adaptable, modelClass); |
| } |
| |
| private boolean internalCanCreateFromAdaptable(Object adaptable, Class<?> requestedType) throws ModelClassException { |
| try { |
| ModelClass<?> modelClass = getImplementationTypeForAdapterType(requestedType, adaptable); |
| Class<?>[] declaredAdaptable = modelClass.getModelAnnotation().adaptables(); |
| for (Class<?> clazz : declaredAdaptable) { |
| if (clazz.isInstance(adaptable)) { |
| return true; |
| } |
| } |
| } catch (ModelClassException e) { |
| log.debug("Could not find implementation for given type " + requestedType + ". Probably forgot either the model annotation or it was not registered as adapter factory (yet)", e); |
| return false; |
| } |
| return false; |
| } |
| |
| @Override |
| @Deprecated |
| public boolean isModelClass(@NotNull Object adaptable, @NotNull Class<?> requestedType) { |
| try { |
| getImplementationTypeForAdapterType(requestedType, adaptable); |
| } catch (ModelClassException e) { |
| log.debug("Could not find implementation for given adaptable. Probably forgot either the model annotation or it was not registered as adapter factory (yet)", e); |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean isModelClass(@NotNull Class<?> type) { |
| return this.adapterImplementations.isModelClass(type); |
| } |
| |
| /** |
| * |
| * @param requestedType the adapter type |
| * @param adaptable the adaptable |
| * @return the implementation type to use for the desired model type or null if there is none registered |
| * @see <a |
| * href="http://sling.apache.org/documentation/bundles/models.html#specifying-an-alternate-adapter-class-since-sling-models-110">Specifying |
| * an Alternate Adapter Class</a> |
| */ |
| private <ModelType> ModelClass<ModelType> getImplementationTypeForAdapterType(Class<ModelType> requestedType, Object adaptable) { |
| // lookup ModelClass wrapper for implementation type |
| // additionally check if a different implementation class was registered for this adapter type |
| // the adapter implementation is initially filled by the ModelPackageBundleList |
| ModelClass<ModelType> modelClass = this.adapterImplementations.lookup(requestedType, adaptable); |
| if (modelClass != null) { |
| log.debug("Using implementation type {} for requested adapter type {}", modelClass, requestedType); |
| return modelClass; |
| } |
| // throw exception here |
| throw new ModelClassException("Could not yet find an adapter factory for the model " + requestedType + " from adaptable " + adaptable.getClass()); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Map<Class<?>, SoftReference<Object>> getOrCreateCache(final Object adaptable) { |
| Map<Class<?>, SoftReference<Object>> adaptableCache; |
| if (adaptable instanceof ServletRequest) { |
| ServletRequest request = (ServletRequest) adaptable; |
| adaptableCache = (Map<Class<?>, SoftReference<Object>>) request.getAttribute(REQUEST_CACHE_ATTRIBUTE); |
| if (adaptableCache == null) { |
| adaptableCache = Collections.synchronizedMap(new WeakHashMap<>()); |
| request.setAttribute(REQUEST_CACHE_ATTRIBUTE, adaptableCache); |
| } |
| } else { |
| adaptableCache = adapterCache.computeIfAbsent(adaptable, k -> Collections.synchronizedMap(new WeakHashMap<>())); |
| } |
| return adaptableCache; |
| } |
| |
| |
| private <ModelType> Result<ModelType> internalCreateModelMeasured(@NotNull Object adaptable, @NotNull Class<ModelType> type) { |
| Result<ModelType> result; |
| if (perfLog.isDebugEnabled()) { |
| long startTime = perfLog.isDebugEnabled() ? System.nanoTime() : -1; |
| ThreadInvocationCounter threadInvocationCounter = invocationCountThreadLocal.get(); |
| threadInvocationCounter.resetStats(); |
| |
| result = internalCreateModel(adaptable, type); |
| |
| double duration = ((double) System.nanoTime() - startTime) / 1000000.0; |
| |
| perfLog.debug("createdModel({},{}) took {}ms, instantiations {}, max recursion {}", |
| adaptable.getClass().getName(), |
| type.getName(), |
| duration, |
| threadInvocationCounter.getOverallCount(), |
| threadInvocationCounter.getMaxRecursion()); |
| } else { |
| result = internalCreateModel(adaptable, type); |
| } |
| |
| return result; |
| } |
| |
| @SuppressWarnings("unchecked") |
| <ModelType> Result<ModelType> internalCreateModel(final Object adaptable, final Class<ModelType> requestedType) { |
| Result<ModelType> result; |
| ThreadInvocationCounter threadInvocationCounter = invocationCountThreadLocal.get(); |
| if (threadInvocationCounter.isMaximumReached()) { |
| String msg = String.format("Adapting %s to %s failed, too much recursive invocations (>=%s).", |
| adaptable, requestedType, threadInvocationCounter.maxRecursionDepth); |
| return new Result<>(new ModelClassException(msg)); |
| } |
| threadInvocationCounter.increase(); |
| try { |
| // check if a different implementation class was registered for this adapter type |
| ModelClass<ModelType> modelClass = getImplementationTypeForAdapterType(requestedType, adaptable); |
| |
| if (!modelClass.hasModelAnnotation()) { |
| String msg = String.format("Provided Adapter class does not have a Model annotation: %s", modelClass.getType()); |
| return new Result<>(new ModelClassException(msg)); |
| } |
| boolean isAdaptable = false; |
| |
| Model modelAnnotation = modelClass.getModelAnnotation(); |
| Map<Class<?>, SoftReference<Object>> adaptableCache = null; |
| |
| if (modelAnnotation.cache()) { |
| adaptableCache = getOrCreateCache(adaptable); |
| SoftReference<Object> softReference = adaptableCache.get(modelClass.getType()); |
| if (softReference != null) { |
| ModelType cachedObject = (ModelType) softReference.get(); |
| if (cachedObject != null) { |
| return new Result<>(cachedObject); |
| } |
| } |
| } |
| |
| Class<?>[] declaredAdaptable = modelAnnotation.adaptables(); |
| for (Class<?> clazz : declaredAdaptable) { |
| if (clazz.isInstance(adaptable)) { |
| isAdaptable = true; |
| } |
| } |
| if (!isAdaptable) { |
| String msg = String.format("Adaptables (%s) are not acceptable for the model class: %s", StringUtils.join(declaredAdaptable), modelClass.getType()); |
| return new Result<>(new InvalidAdaptableException(msg)); |
| } else { |
| RuntimeException t = validateModel(adaptable, modelClass.getType(), modelAnnotation); |
| if (t != null) { |
| return new Result<>(t); |
| } |
| if (modelClass.getType().isInterface()) { |
| Result<InvocationHandler> handlerResult = createInvocationHandler(adaptable, modelClass); |
| if (handlerResult.wasSuccessful()) { |
| ModelType model = (ModelType) Proxy.newProxyInstance(modelClass.getType().getClassLoader(), new Class<?>[] { modelClass.getType() }, handlerResult.getValue()); |
| |
| if (modelAnnotation.cache() && adaptableCache != null) { |
| adaptableCache.put(modelClass.getType(), new SoftReference<>(model)); |
| } |
| |
| result = new Result<>(model); |
| } else { |
| return new Result<>(handlerResult.getThrowable()); |
| } |
| } else { |
| try { |
| result = createObject(adaptable, modelClass); |
| |
| if (result.wasSuccessful() && modelAnnotation.cache() && adaptableCache != null) { |
| adaptableCache.put(modelClass.getType(), new SoftReference<>(result.getValue())); |
| } |
| } catch (Exception e) { |
| String msg = String.format("Unable to create model %s", modelClass.getType()); |
| return new Result<>(new ModelClassException(msg, e)); |
| } |
| } |
| } |
| return result; |
| } finally { |
| threadInvocationCounter.decrease(); |
| } |
| } |
| |
| private <ModelType> RuntimeException validateModel(Object adaptable, Class<ModelType> modelType, Model modelAnnotation) { |
| if (modelAnnotation.validation() != ValidationStrategy.DISABLED) { |
| if (modelValidation == null) { |
| return new ValidationException("No active service for ModelValidation found, therefore no validation can be performed."); |
| } |
| return modelValidation.validate(adaptable, modelType, modelAnnotation.validation() == ValidationStrategy.REQUIRED); |
| } |
| return null; |
| } |
| |
| private interface InjectCallback { |
| /** |
| * Is called each time when the given value should be injected into the given element |
| * @param element |
| * @param value |
| * @return an InjectionResult |
| */ |
| RuntimeException inject(InjectableElement element, Object value); |
| } |
| |
| private class SetFieldCallback implements InjectCallback { |
| |
| private final Object object; |
| |
| private SetFieldCallback(Object object) { |
| this.object = object; |
| } |
| |
| @Override |
| public RuntimeException inject(InjectableElement element, Object value) { |
| return setField((InjectableField) element, object, value); |
| } |
| } |
| |
| private class SetMethodsCallback implements InjectCallback { |
| |
| private final Map<Method, Object> methods; |
| |
| private SetMethodsCallback( Map<Method, Object> methods) { |
| this.methods = methods; |
| } |
| |
| @Override |
| public RuntimeException inject(InjectableElement element, Object value) { |
| return setMethod((InjectableMethod) element, methods, value); |
| } |
| } |
| |
| private class SetConstructorParameterCallback implements InjectCallback { |
| |
| private final List<Object> parameterValues; |
| |
| private SetConstructorParameterCallback(List<Object> parameterValues) { |
| this.parameterValues = parameterValues; |
| } |
| |
| @Override |
| public RuntimeException inject(InjectableElement element, Object value) { |
| return setConstructorParameter((ConstructorParameter)element, parameterValues, value); |
| } |
| } |
| |
| private class OptionalWrappingCallback implements InjectCallback { |
| |
| private final InjectCallback chainedCallback; |
| private final InjectableElement element; |
| |
| private OptionalWrappingCallback(InjectCallback chainedCallback, InjectableElement element) { |
| this.chainedCallback = chainedCallback; |
| this.element = element; |
| } |
| |
| @Override |
| public RuntimeException inject(InjectableElement element1, Object value) { |
| return chainedCallback.inject(element, value.equals(Optional.empty()) |
| ? value // if the value is null it's already represented as Optional.empty(), return as is |
| : Optional.of(value)); // otherwise wrap in Optional |
| } |
| } |
| |
| private |
| @Nullable |
| RuntimeException injectElement(final InjectableElement element, final Object adaptable, |
| final @NotNull DisposalCallbackRegistry registry, final InjectCallback callback, |
| final @NotNull Map<ValuePreparer, Object> preparedValues, |
| final @Nullable BundleContext modelContext) { |
| if (element instanceof InjectableField) { |
| Type genericType = ((InjectableField) element).getFieldGenericType(); |
| |
| if (genericType instanceof ParameterizedType) { |
| ParameterizedType pType = (ParameterizedType) genericType; |
| |
| if (pType.getRawType().equals(Optional.class)) { |
| InjectableElement el = new OptionalTypedInjectableElement(element, pType.getActualTypeArguments()[0]); |
| InjectCallback wrappedCallback = new OptionalWrappingCallback(callback, element); |
| |
| return injectElementInternal(el, adaptable, registry, wrappedCallback, preparedValues, modelContext); |
| } |
| } |
| } |
| |
| return injectElementInternal(element, adaptable, registry, callback, preparedValues, modelContext); |
| } |
| |
| private |
| @Nullable |
| RuntimeException injectElementInternal(final InjectableElement element, final Object adaptable, |
| final @NotNull DisposalCallbackRegistry registry, final InjectCallback callback, |
| final @NotNull Map<ValuePreparer, Object> preparedValues, |
| final @Nullable BundleContext modelContext) { |
| |
| InjectAnnotationProcessor annotationProcessor = null; |
| String source = element.getSource(); |
| boolean wasInjectionSuccessful = false; |
| |
| // find an appropriate annotation processor |
| for (InjectAnnotationProcessorFactory2 factory : injectAnnotationProcessorFactories2) { |
| annotationProcessor = factory.createAnnotationProcessor(adaptable, element.getAnnotatedElement()); |
| if (annotationProcessor != null) { |
| break; |
| } |
| } |
| if (annotationProcessor == null) { |
| for (InjectAnnotationProcessorFactory factory : injectAnnotationProcessorFactories) { |
| annotationProcessor = factory.createAnnotationProcessor(adaptable, element.getAnnotatedElement()); |
| if (annotationProcessor != null) { |
| break; |
| } |
| } |
| } |
| |
| String name = getName(element, annotationProcessor); |
| final Object injectionAdaptable = getAdaptable(adaptable, element, annotationProcessor); |
| |
| RuntimeException lastInjectionException = null; |
| if (injectionAdaptable != null) { |
| |
| // prepare the set of injectors to process. if a source is given only use injectors with this name. |
| final RankedServices<Injector> injectorsToProcess; |
| if (StringUtils.isEmpty(source)) { |
| injectorsToProcess = sortedInjectors; |
| } |
| else { |
| injectorsToProcess = injectors.get(source); |
| if (injectorsToProcess == null) { |
| throw new IllegalArgumentException("No Sling Models Injector registered for source '" + source + "'."); |
| } |
| } |
| |
| // find the right injector |
| for (Injector injector : injectorsToProcess) { |
| if (name != null || injector instanceof AcceptsNullName) { |
| Object preparedValue = injectionAdaptable; |
| |
| // only do the ValuePreparer optimization for the original adaptable |
| if (injector instanceof ValuePreparer && adaptable == injectionAdaptable) { |
| final ValuePreparer preparer = (ValuePreparer) injector; |
| Object fromMap = preparedValues.get(preparer); |
| if (fromMap != null) { |
| preparedValue = fromMap; |
| } else { |
| preparedValue = preparer.prepareValue(injectionAdaptable); |
| preparedValues.put(preparer, preparedValue); |
| } |
| } |
| Object value = null; |
| if (injector instanceof OSGiServiceInjector) { |
| value = ((OSGiServiceInjector)injector).getValue(preparedValue, name, element.getType(), element.getAnnotatedElement(), registry, modelContext); |
| } else { |
| value = injector.getValue(preparedValue, name, element.getType(), element.getAnnotatedElement(), registry); |
| } |
| if (value != null) { |
| lastInjectionException = callback.inject(element, value); |
| if (lastInjectionException == null) { |
| wasInjectionSuccessful = true; |
| break; |
| } |
| } |
| } |
| } |
| } |
| // if injection failed, use default |
| if (!wasInjectionSuccessful) { |
| Result<Boolean> defaultInjectionResult = injectDefaultValue(element, annotationProcessor, callback); |
| if (defaultInjectionResult.wasSuccessful()) { |
| wasInjectionSuccessful = defaultInjectionResult.getValue(); |
| // log previous injection error, if there was any |
| if (lastInjectionException != null && wasInjectionSuccessful) { |
| log.debug("Although falling back to default value worked, injection into {} failed because of: " + lastInjectionException.getMessage(), element.getAnnotatedElement(), lastInjectionException); |
| } |
| } else { |
| return defaultInjectionResult.getThrowable(); |
| } |
| } |
| |
| // if default is not set, check if mandatory |
| if (!wasInjectionSuccessful) { |
| if (element.isOptional(annotationProcessor)) { |
| // log previous injection error, if there was any |
| if (lastInjectionException != null) { |
| log.debug("Injection into optional element {} failed because of: " + lastInjectionException.getMessage(), element.getAnnotatedElement(), lastInjectionException); |
| } |
| if (element.isPrimitive()) { |
| RuntimeException throwable = injectPrimitiveInitialValue(element, callback); |
| if (throwable != null) { |
| return throwable; |
| } |
| } |
| } else { |
| if (lastInjectionException != null) { |
| return lastInjectionException; |
| } else { |
| return new ModelClassException("No injector returned a non-null value!"); |
| } |
| } |
| } |
| return null; |
| } |
| |
| private BundleContext getModelBundleContext(final ModelClass<?> modelClass) { |
| Bundle modelBundle = FrameworkUtil.getBundle(modelClass.getType()); |
| if (modelBundle != null) { |
| return modelBundle.getBundleContext(); |
| } |
| return null; |
| } |
| |
| private <ModelType> Result<InvocationHandler> createInvocationHandler(final Object adaptable, final ModelClass<ModelType> modelClass) { |
| InjectableMethod[] injectableMethods = modelClass.getInjectableMethods(); |
| final Map<Method, Object> methods = new HashMap<>(); |
| SetMethodsCallback callback = new SetMethodsCallback(methods); |
| MapBackedInvocationHandler handler = new MapBackedInvocationHandler(methods); |
| |
| DisposalCallbackRegistryImpl registry = new DisposalCallbackRegistryImpl(); |
| |
| final Map<ValuePreparer, Object> preparedValues = new HashMap<>(VALUE_PREPARERS_COUNT); |
| List<MissingElementException> missingElements = null; |
| final BundleContext modelContext = getModelBundleContext(modelClass); |
| for (InjectableMethod method : injectableMethods) { |
| RuntimeException t = injectElement(method, adaptable, registry, callback, preparedValues, modelContext); |
| if (t != null) { |
| if (missingElements == null) { |
| missingElements = new ArrayList<>(); |
| } |
| missingElements.add(new MissingElementException(method.getAnnotatedElement(), t)); |
| } |
| } |
| |
| if (!registry.callbacks.isEmpty()) { |
| registry.seal(); |
| |
| if (adaptable instanceof SlingHttpServletRequest && |
| ((SlingHttpServletRequest) adaptable).getAttribute(REQUEST_MARKER_ATTRIBUTE) == REQUEST_MARKER_VALUE) { |
| registerRequestCallbackRegistry((SlingHttpServletRequest) adaptable, registry); |
| } else { |
| registerCallbackRegistry(handler, registry); |
| } |
| } |
| if (missingElements != null) { |
| MissingElementsException missingElementsException = new MissingElementsException("Could not create all mandatory methods for interface of model " + modelClass); |
| for (MissingElementException me : missingElements) { |
| missingElementsException.addMissingElementExceptions(me); |
| } |
| return new Result<>(missingElementsException); |
| } |
| return new Result<InvocationHandler>(handler); |
| } |
| |
| private void registerCallbackRegistry(Object object, DisposalCallbackRegistryImpl registry) { |
| PhantomReference<Object> reference = new PhantomReference<>(object, queue); |
| disposalCallbacks.put(reference, registry); |
| } |
| |
| private void registerRequestCallbackRegistry(ServletRequest request, DisposalCallbackRegistryImpl registry) { |
| request = unwrapRequest(request); |
| requestDisposalCallbacks.put(request, registry); |
| } |
| |
| private static ServletRequest unwrapRequest(ServletRequest request) { |
| while (request instanceof ServletRequestWrapper) { |
| request = ((ServletRequestWrapper) request).getRequest(); |
| } |
| return request; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private <ModelType> Result<ModelType> createObject(final Object adaptable, final ModelClass<ModelType> modelClass) |
| throws InstantiationException, InvocationTargetException, IllegalAccessException { |
| DisposalCallbackRegistryImpl registry = new DisposalCallbackRegistryImpl(); |
| |
| ModelClassConstructor<ModelType> constructorToUse = getBestMatchingConstructor(adaptable, modelClass); |
| if (constructorToUse == null) { |
| return new Result<>(new ModelClassException("Unable to find a useable constructor for model " + modelClass.getType())); |
| } |
| |
| final Map<ValuePreparer, Object> preparedValues = new HashMap<>(VALUE_PREPARERS_COUNT); |
| |
| ModelType object; |
| if (constructorToUse.getConstructor().getParameterTypes().length == 0) { |
| // no parameters for constructor injection? instantiate it right away |
| |
| object = constructorToUse.newInstance(); |
| } else { |
| // instantiate with constructor injection |
| // if this fails, make sure resources that may be claimed by injectors are cleared up again |
| try { |
| Result<ModelType> result = newInstanceWithConstructorInjection(constructorToUse, adaptable, modelClass, registry, preparedValues); |
| if (!result.wasSuccessful()) { |
| registry.onDisposed(); |
| return result; |
| } else { |
| object = result.getValue(); |
| } |
| } catch (InstantiationException | InvocationTargetException | IllegalAccessException ex) { |
| registry.onDisposed(); |
| throw ex; |
| } |
| } |
| |
| InjectCallback callback = new SetFieldCallback(object); |
| |
| InjectableField[] injectableFields = modelClass.getInjectableFields(); |
| List<MissingElementException> missingElements = null; |
| final BundleContext modelContext = getModelBundleContext(modelClass); |
| for (InjectableField field : injectableFields) { |
| RuntimeException t = injectElement(field, adaptable, registry, callback, preparedValues, modelContext); |
| if (t != null) { |
| if (missingElements == null) { |
| missingElements = new ArrayList<>(); |
| } |
| missingElements.add(new MissingElementException(field.getAnnotatedElement(), t)); |
| } |
| } |
| |
| if (!registry.callbacks.isEmpty()) { |
| registry.seal(); |
| |
| if (adaptable instanceof SlingHttpServletRequest && |
| ((SlingHttpServletRequest) adaptable).getAttribute(REQUEST_MARKER_ATTRIBUTE) == REQUEST_MARKER_VALUE) { |
| registerRequestCallbackRegistry((SlingHttpServletRequest) adaptable, registry); |
| } else { |
| registerCallbackRegistry(object, registry); |
| } |
| |
| } |
| if (missingElements != null) { |
| MissingElementsException missingElementsException = new MissingElementsException("Could not inject all required fields into " + modelClass.getType()); |
| for (MissingElementException me : missingElements) { |
| missingElementsException.addMissingElementExceptions(me); |
| } |
| return new Result<>(missingElementsException); |
| } |
| try { |
| object = invokePostConstruct(object); |
| if (object == null) { |
| return (Result<ModelType>) Result.POST_CONSTRUCT_PREVENTED_MODEL_CONSTRUCTION; |
| } |
| } catch (InvocationTargetException e) { |
| return new Result<>(new PostConstructException("Post-construct method has thrown an exception for model " + modelClass.getType(), e.getCause())); |
| } catch (IllegalAccessException e) { |
| new Result<ModelType>(new ModelClassException("Could not call post-construct method for model " + modelClass.getType(), e)); |
| } |
| return new Result<>(object); |
| } |
| |
| /** |
| * Gets best matching constructor for constructor injection - or default constructor if none is found. |
| * @param adaptable Adaptable instance |
| * @param type Model type |
| * @return Constructor or null if none found |
| */ |
| private <ModelType> ModelClassConstructor<ModelType> getBestMatchingConstructor(Object adaptable, ModelClass<ModelType> type) { |
| ModelClassConstructor<ModelType>[] constructors = type.getConstructors(); |
| |
| for (ModelClassConstructor<ModelType> constructor : constructors) { |
| // first try to find the constructor with most parameters and @Inject annotation |
| if (constructor.hasInjectAnnotation()) { |
| return constructor; |
| } |
| // compatibility mode for sling models implementation <= 1.0.6: |
| // support constructor without @Inject if it has exactly one parameter matching the adaptable class |
| final Class<?>[] paramTypes = constructor.getConstructor().getParameterTypes(); |
| if (paramTypes.length == 1) { |
| Class<?> paramType = constructor.getConstructor().getParameterTypes()[0]; |
| if (paramType.isInstance(adaptable)) { |
| return constructor; |
| } |
| } |
| // if no constructor for injection found use public constructor without any params |
| if (constructor.getConstructor().getParameterTypes().length == 0) { |
| return constructor; |
| } |
| } |
| return null; |
| } |
| |
| private <ModelType> Result<ModelType> newInstanceWithConstructorInjection(final ModelClassConstructor<ModelType> constructor, final Object adaptable, |
| final ModelClass<ModelType> modelClass, final DisposalCallbackRegistry registry, final @NotNull Map<ValuePreparer, Object> preparedValues) |
| throws InstantiationException, InvocationTargetException, IllegalAccessException { |
| ConstructorParameter[] parameters = constructor.getConstructorParameters(); |
| |
| List<Object> paramValues = new ArrayList<>(Arrays.asList(new Object[parameters.length])); |
| InjectCallback callback = new SetConstructorParameterCallback(paramValues); |
| |
| final BundleContext modelContext = getModelBundleContext(modelClass); |
| List<MissingElementException> missingElements = null; |
| for (int i = 0; i < parameters.length; i++) { |
| RuntimeException t = injectElement(parameters[i], adaptable, registry, callback, preparedValues, modelContext); |
| if (t != null) { |
| if (missingElements == null) { |
| missingElements = new ArrayList<>(); |
| } |
| missingElements.add(new MissingElementException(parameters[i].getAnnotatedElement(), t)); |
| } |
| } |
| if (missingElements != null) { |
| MissingElementsException missingElementsException = new MissingElementsException("Required constructor parameters were not able to be injected on model " + modelClass.getType()); |
| for (MissingElementException me : missingElements) { |
| missingElementsException.addMissingElementExceptions(me); |
| } |
| return new Result<>(missingElementsException); |
| } |
| return new Result<>(constructor.newInstance(paramValues.toArray(new Object[paramValues.size()]))); |
| } |
| |
| private Result<Boolean> injectDefaultValue(InjectableElement point, InjectAnnotationProcessor processor, |
| InjectCallback callback) { |
| if (processor != null) { |
| if (processor.hasDefault()) { |
| RuntimeException t = callback.inject(point, processor.getDefault()); |
| if (t == null) { |
| return new Result<>(Boolean.TRUE); |
| } else { |
| return new Result<>(t); |
| } |
| } |
| } |
| |
| Object value = point.getDefaultValue(); |
| if (value != null) { |
| RuntimeException t = callback.inject(point, value); |
| if (t == null) { |
| return new Result<>(Boolean.TRUE); |
| } else { |
| return new Result<>(t); |
| } |
| } |
| else { |
| return new Result<>(Boolean.FALSE); |
| } |
| } |
| |
| /** |
| * Injects the default initial value for the given primitive class which |
| * cannot be null (e.g. int = 0, boolean = false). |
| * |
| * @param point Annotated element |
| * @param callback Inject callback |
| */ |
| private RuntimeException injectPrimitiveInitialValue(InjectableElement point, InjectCallback callback) { |
| Type primitiveType = ReflectionUtil.mapWrapperClasses(point.getType()); |
| Object value = null; |
| if (primitiveType == int.class) { |
| value = 0; |
| } else if (primitiveType == long.class) { |
| value = 0L; |
| } else if (primitiveType == boolean.class) { |
| value = Boolean.FALSE; |
| } else if (primitiveType == double.class) { |
| value = 0.0d; |
| } else if (primitiveType == float.class) { |
| value = 0.0f; |
| } else if (primitiveType == short.class) { |
| value = (short) 0; |
| } else if (primitiveType == byte.class) { |
| value = (byte) 0; |
| } else if (primitiveType == char.class) { |
| value = '\u0000'; |
| } |
| if (value != null) { |
| return callback.inject(point, value); |
| } else { |
| return new ModelClassException(String.format("Unknown primitive type %s", primitiveType.toString())); |
| } |
| } |
| |
| private Object getAdaptable(Object adaptable, InjectableElement point, InjectAnnotationProcessor processor) { |
| String viaValue = null; |
| Class<? extends ViaProviderType> viaProviderType = null; |
| if (processor != null) { |
| viaValue = processor.getVia(); |
| viaProviderType = BeanProperty.class; // processors don't support via provider type |
| } |
| if (StringUtils.isBlank(viaValue)) { |
| viaValue = point.getVia(); |
| viaProviderType = point.getViaProviderType(); |
| } |
| if (viaProviderType == null || viaValue == null) { |
| return adaptable; |
| } |
| ViaProvider viaProvider = viaProviders.get(viaProviderType); |
| if (viaProvider == null) { |
| log.error("Unable to find Via provider type {}.", viaProviderType); |
| return null; |
| } |
| final Object viaResult = viaProvider.getAdaptable(adaptable, viaValue); |
| if (viaResult == ViaProvider.ORIGINAL) { |
| return adaptable; |
| } else { |
| return viaResult; |
| } |
| } |
| |
| private String getName(InjectableElement element, InjectAnnotationProcessor processor) { |
| // try to get the name from injector-specific annotation |
| if (processor != null) { |
| String name = processor.getName(); |
| if (name != null) { |
| return name; |
| } |
| } |
| // get name from @Named annotation or element name |
| return element.getName(); |
| } |
| |
| private boolean addMethodIfNotOverriden(List<Method> methods, Method newMethod) { |
| for (Method method : methods) { |
| if (method.getName().equals(newMethod.getName())) { |
| if (Arrays.equals(method.getParameterTypes(),newMethod.getParameterTypes())) { |
| return false; |
| } |
| } |
| } |
| methods.add(newMethod); |
| return true; |
| } |
| |
| @SuppressWarnings("null") |
| private <ModelType> ModelType invokePostConstruct(ModelType object) throws InvocationTargetException, IllegalAccessException { |
| Class<?> clazz = object.getClass(); |
| List<Method> postConstructMethods = new ArrayList<>(); |
| while (clazz != null) { |
| Method[] methods = clazz.getDeclaredMethods(); |
| for (Method method : methods) { |
| if (method.isAnnotationPresent(PostConstruct.class)) { |
| addMethodIfNotOverriden(postConstructMethods, method); |
| } |
| } |
| clazz = clazz.getSuperclass(); |
| } |
| Collections.reverse(postConstructMethods); |
| for (Method method : postConstructMethods) { |
| method.setAccessible(true); |
| Object result = method.invoke(object); |
| if (result instanceof Boolean && !((Boolean) result).booleanValue()) { |
| log.debug("PostConstruct method {}.{} returned false. Returning null model.", method.getDeclaringClass().getName(), method.getName()); |
| return null; |
| } |
| } |
| return object; |
| } |
| |
| private RuntimeException setField(InjectableField injectableField, Object createdObject, Object value) { |
| Result<Object> result = adaptIfNecessary(value, injectableField.getFieldType(), injectableField.getFieldGenericType()); |
| if (result.wasSuccessful()) { |
| return injectableField.set(createdObject, result); |
| } else { |
| return result.getThrowable(); |
| } |
| } |
| |
| private RuntimeException setMethod(InjectableMethod injectableMethod, Map<Method, Object> methods, Object value) { |
| Method method = injectableMethod.getMethod(); |
| Result<Object> result = adaptIfNecessary(value, method.getReturnType(), method.getGenericReturnType()); |
| if (result.wasSuccessful()) { |
| methods.put(method, result.getValue()); |
| return null; |
| } else { |
| return result.getThrowable(); |
| } |
| } |
| |
| private RuntimeException setConstructorParameter(ConstructorParameter constructorParameter, List<Object> parameterValues, Object value) { |
| if (constructorParameter.getParameterType() instanceof Class<?>) { |
| Result<Object> result = adaptIfNecessary(value, (Class<?>) constructorParameter.getParameterType(), constructorParameter.getGenericType()); |
| if (result.wasSuccessful() ) { |
| parameterValues.set(constructorParameter.getParameterIndex(), result.getValue()); |
| return null; |
| } else { |
| return result.getThrowable(); |
| } |
| } else { |
| return new ModelClassException(String.format("Constructor parameter with index %d is not a class!", constructorParameter.getParameterIndex())); |
| } |
| } |
| |
| @SuppressWarnings("null") |
| private Result<Object> adaptIfNecessary(final Object value, final Class<?> type, final Type genericType) { |
| final Object adaptedValue; |
| if (!isAcceptableType(type, genericType, value)) { |
| if (genericType instanceof ParameterizedType) { |
| ParameterizedType parameterizedType = (ParameterizedType) genericType; |
| if (value instanceof Collection && |
| (type.equals(Collection.class) || type.equals(List.class)) && |
| parameterizedType.getActualTypeArguments().length == 1) { |
| |
| List<Object> result = new ArrayList<>(); |
| for (Object valueObject : (Collection<?>) value) { |
| Result<Object> singleValueResult = adapt(valueObject, (Class<?>) parameterizedType.getActualTypeArguments()[0], true); |
| if (singleValueResult.wasSuccessful()) { |
| result.add(singleValueResult.getValue()); |
| } else { |
| return singleValueResult; |
| } |
| } |
| adaptedValue = result; |
| } else { |
| return new Result<>(new ModelClassException(String.format("%s is neither a parameterized Collection or List", |
| type))); |
| } |
| } else { |
| return adapt(value, type, false); |
| } |
| return new Result<>(adaptedValue); |
| } else { |
| return new Result<>(value); |
| } |
| } |
| |
| /** |
| * Preferably adapt via the {@link ModelFactory} in case the target type is a Sling Model itself, otherwise use regular {@link Adaptable#adaptTo(Class)}. |
| * @param value the object from which to adapt |
| * @param type the target type |
| * @param isWithinCollection |
| * @return a Result either encapsulating an exception or the adapted value |
| */ |
| private @Nullable Result<Object> adapt(final Object value, final Class<?> type, boolean isWithinCollection) { |
| Object adaptedValue = null; |
| final String messageSuffix = isWithinCollection ? " in collection" : ""; |
| if (isModelClass(type) && canCreateFromAdaptable(value, type)) { |
| Result<?> result = internalCreateModel(value, type); |
| if (result.wasSuccessful()) { |
| adaptedValue = result.getValue(); |
| } else { |
| return new Result<>(new ModelClassException( |
| String.format("Could not create model from %s: %s%s", value.getClass(), result.getThrowable().getMessage(), messageSuffix), |
| result.getThrowable())); |
| } |
| } else if (value instanceof Adaptable) { |
| adaptedValue = ((Adaptable) value).adaptTo(type); |
| if (adaptedValue == null) { |
| return new Result<>(new ModelClassException(String.format("Could not adapt from %s to %s%s", value.getClass(), type, messageSuffix))); |
| } |
| } |
| if (adaptedValue != null) { |
| return new Result<>(adaptedValue); |
| } else { |
| return new Result<>(new ModelClassException( |
| String.format("Could not adapt from %s to %s%s, because this class is not adaptable!", value.getClass(), type, messageSuffix))); |
| } |
| } |
| |
| @SuppressWarnings("null") |
| private static boolean isAcceptableType(Class<?> type, Type genericType, Object value) { |
| if (type.isInstance(value)) { |
| if ((type == Collection.class || type == List.class) && genericType instanceof ParameterizedType && |
| value instanceof Collection) { |
| Iterator<?> it = ((Collection<?>) value).iterator(); |
| if (!it.hasNext()) { |
| // empty collection, so it doesn't really matter |
| return true; |
| } else { |
| // this is not an ideal way to get the actual component type, but erasure... |
| Class<?> actualComponentType = it.next().getClass(); |
| Class<?> desiredComponentType = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0]; |
| return desiredComponentType.isAssignableFrom(actualComponentType); |
| } |
| } else { |
| return true; |
| } |
| } |
| |
| if (type == Integer.TYPE) { |
| return Integer.class.isInstance(value); |
| } |
| if (type == Long.TYPE) { |
| return Long.class.isInstance(value); |
| } |
| if (type == Boolean.TYPE) { |
| return Boolean.class.isInstance(value); |
| } |
| if (type == Double.TYPE) { |
| return Double.class.isInstance(value); |
| } |
| if (type == Float.TYPE) { |
| return Float.class.isInstance(value); |
| } |
| if (type == Short.TYPE) { |
| return Short.class.isInstance(value); |
| } |
| if (type == Byte.TYPE) { |
| return Byte.class.isInstance(value); |
| } |
| if (type == Character.TYPE) { |
| return Character.class.isInstance(value); |
| } |
| |
| return false; |
| } |
| |
| @Activate |
| protected void activate(final ComponentContext ctx, final ModelAdapterFactoryConfiguration configuration) { |
| this.invocationCountThreadLocal = new ThreadLocal<ThreadInvocationCounter>() { |
| @Override |
| protected ThreadInvocationCounter initialValue() { |
| return new ThreadInvocationCounter(configuration.max_recursion_depth()); |
| } |
| }; |
| |
| this.adapterCache = Collections.synchronizedMap(new WeakHashMap<Object, Map<Class<?>, SoftReference<Object>>>()); |
| |
| BundleContext bundleContext = ctx.getBundleContext(); |
| this.queue = new ReferenceQueue<>(); |
| this.disposalCallbacks = new ConcurrentHashMap<>(); |
| this.requestDisposalCallbacks = new RequestDisposalCallbacks(); |
| Hashtable<String, Object> properties = new Hashtable<>(); |
| properties.put(Constants.SERVICE_VENDOR, "Apache Software Foundation"); |
| properties.put(Constants.SERVICE_DESCRIPTION, "Sling Models OSGi Service Disposal Job"); |
| properties.put("scheduler.name", "Sling Models OSGi Service Disposal Job"); |
| properties.put("scheduler.concurrent", false); |
| properties.put("scheduler.period", configuration.cleanup_job_period()); |
| |
| this.jobRegistration = bundleContext.registerService(Runnable.class, this, properties); |
| |
| this.scriptEngineFactory = new SlingModelsScriptEngineFactory(bundleContext.getBundle()); |
| this.listener = new ModelPackageBundleListener(ctx.getBundleContext(), this, this.adapterImplementations, bindingsValuesProvidersByContext, scriptEngineFactory); |
| |
| Hashtable<String, Object> printerProps = new Hashtable<>(); |
| printerProps.put(Constants.SERVICE_VENDOR, "Apache Software Foundation"); |
| printerProps.put(Constants.SERVICE_DESCRIPTION, "Sling Models Configuration Printer"); |
| printerProps.put("felix.webconsole.label", "slingmodels"); |
| printerProps.put("felix.webconsole.title", "Sling Models"); |
| printerProps.put("felix.webconsole.configprinter.modes", "always"); |
| |
| this.configPrinterRegistration = bundleContext.registerService(Object.class, |
| new ModelConfigurationPrinter(this, bundleContext, adapterImplementations), printerProps); |
| |
| } |
| |
| @Deactivate |
| protected void deactivate() { |
| this.adapterCache = null; |
| if (this.requestDisposalCallbacks != null) { |
| for (final Disposable requestRegistries : this.requestDisposalCallbacks.values()) { |
| requestRegistries.onDisposed(); |
| } |
| } |
| this.requestDisposalCallbacks = null; |
| this.clearDisposalCallbackRegistryQueue(); |
| this.listener.unregisterAll(); |
| this.adapterImplementations.removeAll(); |
| if (jobRegistration != null) { |
| jobRegistration.unregister(); |
| jobRegistration = null; |
| } |
| if (configPrinterRegistration != null) { |
| configPrinterRegistration.unregister(); |
| configPrinterRegistration = null; |
| } |
| } |
| |
| @Reference(cardinality = ReferenceCardinality.MULTIPLE, |
| policy = ReferencePolicy.DYNAMIC) |
| protected void bindInjector(final Injector injector, final Map<String, Object> props) { |
| RankedServices<Injector> newRankedServices = new RankedServices<>(); |
| RankedServices<Injector> injectorsPerInjectorName = injectors.putIfAbsent(injector.getName(), newRankedServices); |
| if (injectorsPerInjectorName == null) { |
| injectorsPerInjectorName = newRankedServices; |
| } |
| injectorsPerInjectorName.bind(injector, props); |
| sortedInjectors.bind(injector, props); |
| } |
| |
| protected void unbindInjector(final Injector injector, final Map<String, Object> props) { |
| RankedServices<Injector> injectorsPerInjectorName = injectors.get(injector.getName()); |
| if (injectorsPerInjectorName != null) { |
| injectorsPerInjectorName.unbind(injector, props); |
| } |
| sortedInjectors.unbind(injector, props); |
| } |
| |
| @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) |
| protected void bindStaticInjectAnnotationProcessorFactory(final StaticInjectAnnotationProcessorFactory factory, final Map<String, Object> props) { |
| synchronized (staticInjectAnnotationProcessorFactories) { |
| staticInjectAnnotationProcessorFactories.bind(factory, props); |
| this.adapterImplementations.setStaticInjectAnnotationProcessorFactories(staticInjectAnnotationProcessorFactories.get()); |
| } |
| } |
| |
| protected void unbindStaticInjectAnnotationProcessorFactory(final StaticInjectAnnotationProcessorFactory factory, final Map<String, Object> props) { |
| synchronized (staticInjectAnnotationProcessorFactories) { |
| staticInjectAnnotationProcessorFactories.unbind(factory, props); |
| this.adapterImplementations.setStaticInjectAnnotationProcessorFactories(staticInjectAnnotationProcessorFactories.get()); |
| } |
| } |
| |
| @Reference(name="implementationPicker", cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) |
| protected void bindImplementationPicker(final ImplementationPicker implementationPicker, final Map<String, Object> props) { |
| synchronized (implementationPickers) { |
| implementationPickers.bind(implementationPicker, props); |
| this.adapterImplementations.setImplementationPickers(implementationPickers.get()); |
| } |
| } |
| |
| protected void unbindImplementationPicker(final ImplementationPicker implementationPicker, final Map<String, Object> props) { |
| synchronized (implementationPickers) { |
| implementationPickers.unbind(implementationPicker, props); |
| this.adapterImplementations.setImplementationPickers(implementationPickers.get()); |
| } |
| } |
| |
| @Reference( |
| name = "viaProvider", |
| cardinality = ReferenceCardinality.MULTIPLE, |
| policy = ReferencePolicy.DYNAMIC |
| ) |
| protected void bindViaProvider(final ViaProvider viaProvider, final Map<String, Object> props) { |
| Class<? extends ViaProviderType> type = viaProvider.getType(); |
| viaProviders.put(type, viaProvider); |
| } |
| |
| protected void unbindViaProvider(final ViaProvider viaProvider, final Map<String, Object> props) { |
| Class<? extends ViaProviderType> type = viaProvider.getType(); |
| viaProviders.remove(type, viaProvider); |
| } |
| |
| @NotNull Collection<Injector> getInjectors() { |
| return sortedInjectors.get(); |
| } |
| |
| @NotNull Collection<InjectAnnotationProcessorFactory> getInjectAnnotationProcessorFactories() { |
| return injectAnnotationProcessorFactories; |
| } |
| |
| @NotNull Collection<InjectAnnotationProcessorFactory2> getInjectAnnotationProcessorFactories2() { |
| return injectAnnotationProcessorFactories2; |
| } |
| |
| @NotNull Collection<StaticInjectAnnotationProcessorFactory> getStaticInjectAnnotationProcessorFactories() { |
| return staticInjectAnnotationProcessorFactories.get(); |
| } |
| |
| @NotNull ImplementationPicker[] getImplementationPickers() { |
| return adapterImplementations.getImplementationPickers(); |
| } |
| |
| @NotNull Map<Class<? extends ViaProviderType>, ViaProvider> getViaProviders() { |
| return viaProviders; |
| } |
| |
| @Override |
| public boolean isModelAvailableForRequest(@NotNull SlingHttpServletRequest request) { |
| return adapterImplementations.getModelClassForRequest(request) != null; |
| } |
| |
| @Override |
| public boolean isModelAvailableForResource(@NotNull Resource resource) { |
| return adapterImplementations.getModelClassForResource(resource) != null; |
| } |
| |
| @Override |
| public Object getModelFromResource(@NotNull Resource resource) { |
| Class<?> clazz = this.adapterImplementations.getModelClassForResource(resource); |
| if (clazz == null) { |
| throw new ModelClassException("Could find model registered for resource type: " + resource.getResourceType()); |
| } |
| return handleBoundModelResult(internalCreateModelMeasured(resource, clazz)); |
| } |
| |
| @Override |
| public Object getModelFromRequest(@NotNull SlingHttpServletRequest request) { |
| Class<?> clazz = this.adapterImplementations.getModelClassForRequest(request); |
| if (clazz == null) { |
| throw new ModelClassException("Could find model registered for request path: " + request.getServletPath()); |
| } |
| return handleBoundModelResult(internalCreateModelMeasured(request, clazz)); |
| } |
| |
| private Object handleBoundModelResult(Result<?> result) { |
| if (!result.wasSuccessful()) { |
| throw result.getThrowable(); |
| } else { |
| return result.getValue(); |
| } |
| } |
| |
| @Override |
| @SuppressWarnings("null") |
| public <T> T exportModel(Object model, String name, Class<T> targetClass, Map<String, String> options) |
| throws ExportException, MissingExporterException { |
| for (ModelExporter exporter : modelExporters) { |
| if (exporter.getName().equals(name) && exporter.isSupported(targetClass)) { |
| T resultObject = exporter.export(model, targetClass, options); |
| return resultObject; |
| } |
| } |
| throw new MissingExporterException(name, targetClass); |
| } |
| |
| @Override |
| public <T> T exportModelForResource(Resource resource, String name, Class<T> targetClass, Map<String, String> options) |
| throws ExportException, MissingExporterException { |
| Class<?> clazz = this.adapterImplementations.getModelClassForResource(resource); |
| if (clazz == null) { |
| throw new ModelClassException("Could find model registered for resource type: " + resource.getResourceType()); |
| } |
| Result<?> result = internalCreateModelMeasured(resource, clazz); |
| return handleAndExportResult(result, name, targetClass, options); |
| } |
| |
| @Override |
| public <T> T exportModelForRequest(SlingHttpServletRequest request, String name, Class<T> targetClass, Map<String, String> options) |
| throws ExportException, MissingExporterException { |
| Class<?> clazz = this.adapterImplementations.getModelClassForRequest(request); |
| if (clazz == null) { |
| throw new ModelClassException("Could find model registered for request path: " + request.getServletPath()); |
| } |
| Result<?> result = internalCreateModelMeasured(request, clazz); |
| return handleAndExportResult(result, name, targetClass, options); |
| } |
| |
| protected <T> T handleAndExportResult(Result<?> result, String name, Class<T> targetClass, Map<String, String> options) throws ExportException, MissingExporterException { |
| if (result.wasSuccessful()) { |
| return exportModel(result.getValue(), name, targetClass, options); |
| } else { |
| throw result.getThrowable(); |
| } |
| } |
| |
| @Override |
| public <T> T getModelFromWrappedRequest(@NotNull SlingHttpServletRequest request, @NotNull Resource resource, @NotNull Class<T> targetClass) { |
| return new ResourceOverridingRequestWrapper(request, resource, adapterManager, |
| scriptEngineFactory, bindingsValuesProvidersByContext).adaptTo(targetClass); |
| } |
| |
| @Override |
| public void requestDestroyed(ServletRequestEvent sre) { |
| ServletRequest request = unwrapRequest(sre.getServletRequest()); |
| Disposable registry = requestDisposalCallbacks.remove(request); |
| if (registry != null) { |
| registry.onDisposed(); |
| } |
| } |
| |
| @Override |
| public void requestInitialized(ServletRequestEvent sre) { |
| sre.getServletRequest().setAttribute(REQUEST_MARKER_ATTRIBUTE, REQUEST_MARKER_VALUE); |
| } |
| } |