/** * Copyright 2013 the original author or authors. * * Licensed 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 io.neba.core.resourcemodels.adaptation; import io.neba.core.resourcemodels.registration.ModelRegistry; import io.neba.core.util.OsgiBeanSource; import org.apache.sling.api.adapter.AdapterFactory; import org.apache.sling.api.resource.Resource; import org.eclipse.gemini.blueprint.context.BundleContextAware; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.*; import java.util.stream.Collectors; import static org.apache.commons.lang.ClassUtils.getAllInterfaces; import static org.apache.commons.lang.ClassUtils.getAllSuperclasses; import static org.apache.sling.api.adapter.AdapterFactory.ADAPTABLE_CLASSES; import static org.apache.sling.api.adapter.AdapterFactory.ADAPTER_CLASSES; import static org.osgi.framework.Bundle.ACTIVE; import static org.osgi.framework.Bundle.STARTING; /** * An {@link AdapterFactory} provides the {@link AdapterFactory#ADAPTABLE_CLASSES type(s) it adapts from} * and the {@link AdapterFactory#ADAPTER_CLASSES types it can adapt to} as OSGi service * properties. This information is used by {@link org.apache.sling.api.adapter.Adaptable} types to * {@link org.apache.sling.api.adapter.Adaptable#adaptTo(Class) adapt to} * other types, i.e. is essentially a factory pattern. * <br /> * This service registers the {@link ResourceToModelAdapter} as * an {@link AdapterFactory} OSGi service and dynamically updates the before mentioned * service properties with regard to the resource models detected by the * {@link io.neba.core.resourcemodels.registration.ModelRegistrar}. * This enables direct {@link Resource#adaptTo(Class) adaptation} to the resource * models without having to provide all available models as service metadata at build time. * * @author Olaf Otto */ @Service public class ResourceToModelAdapterUpdater implements BundleContextAware { private final Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private ModelRegistry registry; @Autowired private ResourceToModelAdapter adapter; private BundleContext context = null; private ServiceRegistration resourceToModelAdapterRegistration = null; @Async("singlethreaded") public void refresh() { if (isModelAdapterUpdatable()) { updateModeAdapter(); } } /** * Depending on the bundle lifecycle, an OSGi service may not always be * updatable. * * @return true if the {@link ResourceToModelAdapter} OSGi service may be altered. */ private boolean isModelAdapterUpdatable() { int bundleState = this.context.getBundle().getState(); return bundleState == ACTIVE || bundleState == STARTING; } /** * Sling does not detect changes to the state of an {@link AdapterFactory} service. Their * properties are only read when the service is registered. Thus * the service is unregistered and re-registered when changing its properties * (e.g. adding new adaptable types). */ private void updateModeAdapter() { unregisterModelAdapter(); registerModelAdapter(); } /** * {@link BundleContext#registerService(String, Object, Dictionary) Registers} * the {@link ResourceToModelAdapter}, i.e. publishes it as an OSGi service. */ @PostConstruct public void registerModelAdapter() { Dictionary<String, Object> properties = createResourceToModelAdapterProperties(); String serviceClassName = AdapterFactory.class.getName(); this.resourceToModelAdapterRegistration = this.context.registerService(serviceClassName, this.adapter, properties); } private void unregisterModelAdapter() { try { this.resourceToModelAdapterRegistration.unregister(); } catch (IllegalStateException e) { this.logger.info("The resource to model adapter was already unregistered, ignoring.", e); } } private Dictionary<String, Object> createResourceToModelAdapterProperties() { Dictionary<String, Object> properties = new Hashtable<>(); Set<String> fullyQualifiedNamesOfRegisteredModels = getAdapterTypeNames(); properties.put(ADAPTER_CLASSES, fullyQualifiedNamesOfRegisteredModels.toArray()); properties.put(ADAPTABLE_CLASSES, new String[] { Resource.class.getName() }); properties.put("service.vendor", "neba.io"); properties.put("service.description", "Adapts Resources to @ResourceModels."); return properties; } /** * Obtains all {@link OsgiBeanSource bean sources} from the * {@link io.neba.core.resourcemodels.registration.ModelRegistrar} and adds the {@link OsgiBeanSource#getBeanType() * model type name} as well as the type name of all of its superclasses and * interfaces to the set. * * @return never null but rather an empty set. * * @see org.apache.commons.lang.ClassUtils#getAllInterfaces(Class) * @see org.apache.commons.lang.ClassUtils#getAllSuperclasses(Class) */ @SuppressWarnings("unchecked") private Set<String> getAdapterTypeNames() { List<OsgiBeanSource<?>> beanSources = this.registry.getBeanSources(); Set<String> modelNames = new HashSet<>(); for (OsgiBeanSource<?> source : beanSources) { Class<?> c = source.getBeanType(); modelNames.add(c.getName()); modelNames.addAll(toClassnameList(getAllInterfaces(c))); List<Class<?>> allSuperclasses = getAllSuperclasses(c); // Remove Object.class - it is always the topmost element. allSuperclasses.remove(allSuperclasses.size() - 1); modelNames.addAll(toClassnameList(allSuperclasses)); } return modelNames; } private Collection<String> toClassnameList(List<Class<?>> l) { List<String> classNames = new ArrayList<>(l.size()); classNames.addAll(l.stream().map(Class::getName).collect(Collectors.toList())); return classNames; } @Override public void setBundleContext(BundleContext bundleContext) { this.context = bundleContext; } }