Java tutorial
/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.component.embed; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; import javax.inject.Provider; import org.apache.commons.lang3.ObjectUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xwiki.component.annotation.ComponentAnnotationLoader; import org.xwiki.component.descriptor.ComponentDependency; import org.xwiki.component.descriptor.ComponentDescriptor; import org.xwiki.component.descriptor.ComponentInstantiationStrategy; import org.xwiki.component.descriptor.DefaultComponentDescriptor; import org.xwiki.component.internal.RoleHint; import org.xwiki.component.manager.ComponentEventManager; import org.xwiki.component.manager.ComponentLifecycleException; import org.xwiki.component.manager.ComponentLookupException; import org.xwiki.component.manager.ComponentManager; import org.xwiki.component.manager.ComponentManagerInitializer; import org.xwiki.component.manager.ComponentRepositoryException; import org.xwiki.component.phase.Disposable; import org.xwiki.component.util.ReflectionUtils; /** * Simple implementation of {@link ComponentManager} to be used when using some XWiki modules standalone. * * @version $Id: 0d10d72dcfd45dc60eb7e8a60cd4f3593a4c52e3 $ * @since 2.0M1 */ public class EmbeddableComponentManager implements ComponentManager, Disposable { private ComponentEventManager eventManager; /** * Used as fallback for lookup methods. */ private ComponentManager parent; private static class ComponentEntry<R> { /** * Descriptor of the component. */ public final ComponentDescriptor<R> descriptor; /** * Cached instance of the component. Lazily initialized when needed. * <p> * This variable can be accesses and modified by many different threads at the same time so we make it volatile * to ensure it's really shared and sync between all of them and not in each thread memory. */ public volatile R instance; public ComponentEntry(ComponentDescriptor<R> descriptor, R instance) { this.descriptor = descriptor; this.instance = instance; } } private Map<RoleHint<?>, ComponentEntry<?>> componentEntries = new ConcurrentHashMap<RoleHint<?>, ComponentEntry<?>>(); private Logger logger = LoggerFactory.getLogger(EmbeddableComponentManager.class); /** * Finds all lifecycle handlers to use when instantiating a Component. */ private ServiceLoader<LifecycleHandler> lifecycleHandlers = ServiceLoader.load(LifecycleHandler.class); public EmbeddableComponentManager() { registerThis(); } /** * Allow to lookup the this as default {@link ComponentManager} implementation. */ private void registerThis() { DefaultComponentDescriptor<ComponentManager> cd = new DefaultComponentDescriptor<ComponentManager>(); cd.setRoleType(ComponentManager.class); registerComponent(cd, this); } /** * Load all component annotations and register them as components. * * @param classLoader the class loader to use to look for component definitions */ public void initialize(ClassLoader classLoader) { ComponentAnnotationLoader loader = new ComponentAnnotationLoader(); loader.initialize(this, classLoader); // Extension point to allow component to manipulate ComponentManager initialized state. try { List<ComponentManagerInitializer> initializers = this .getInstanceList(ComponentManagerInitializer.class); for (ComponentManagerInitializer initializer : initializers) { initializer.initialize(this); } } catch (ComponentLookupException e) { // Should never happen this.logger.error("Failed to lookup ComponentManagerInitializer components", e); } } @Override public boolean hasComponent(Type role) { return hasComponent(role, "default"); } @Override public boolean hasComponent(Type role, String hint) { if (this.componentEntries.containsKey(new RoleHint<Object>(role, hint))) { return true; } return getParent() != null ? getParent().hasComponent(role, hint) : false; } @Override public <T> T getInstance(Type roleType) throws ComponentLookupException { return getComponentInstance(new RoleHint<T>(roleType)); } @Override public <T> T getInstance(Type roleType, String roleHint) throws ComponentLookupException { return getComponentInstance(new RoleHint<T>(roleType, roleHint)); } @Override public <T> List<T> getInstanceList(Type role) throws ComponentLookupException { // Reuse getInstanceMap to make sure to not return components from parent Component Manager overridden by this // Component Manager Map<String, T> objects = getInstanceMap(role); return objects.isEmpty() ? Collections.<T>emptyList() : new ArrayList<T>(objects.values()); } @Override @SuppressWarnings("unchecked") public <T> Map<String, T> getInstanceMap(Type role) throws ComponentLookupException { Map<String, T> objects = new HashMap<String, T>(); for (Map.Entry<RoleHint<?>, ComponentEntry<?>> entry : this.componentEntries.entrySet()) { RoleHint<?> roleHint = entry.getKey(); if (role.equals(roleHint.getRoleType())) { try { objects.put(roleHint.getHint(), getComponentInstance((ComponentEntry<T>) entry.getValue())); } catch (Exception e) { throw new ComponentLookupException("Failed to lookup component [" + roleHint + "]", e); } } } // Add parent's list of components if (getParent() != null) { // If the hint already exists in the children Component Manager then don't add the one from the parent. for (Map.Entry<String, T> entry : getParent().<T>getInstanceMap(role).entrySet()) { if (!objects.containsKey(entry.getKey())) { objects.put(entry.getKey(), entry.getValue()); } } } return objects; } @Override @SuppressWarnings("unchecked") public <T> ComponentDescriptor<T> getComponentDescriptor(Type role, String hint) { ComponentDescriptor<T> result = null; ComponentEntry<T> componentEntry = (ComponentEntry<T>) this.componentEntries .get(new RoleHint<T>(role, hint)); if (componentEntry == null) { // Check in parent! if (getParent() != null) { result = getParent().getComponentDescriptor(role, hint); } } else { result = componentEntry.descriptor; } return result; } @Override @SuppressWarnings("unchecked") public <T> List<ComponentDescriptor<T>> getComponentDescriptorList(Type role) { Map<String, ComponentDescriptor<T>> descriptors = new HashMap<String, ComponentDescriptor<T>>(); for (Map.Entry<RoleHint<?>, ComponentEntry<?>> entry : this.componentEntries.entrySet()) { if (entry.getKey().getRoleType().equals(role)) { descriptors.put(entry.getKey().getHint(), (ComponentDescriptor<T>) entry.getValue().descriptor); } } // Add Component Descriptors found in parent first if (getParent() != null) { List<ComponentDescriptor<T>> parentDescriptors = getParent().getComponentDescriptorList(role); for (ComponentDescriptor<T> parentDescriptor : parentDescriptors) { // If the hint already exists in the children Component Manager then don't add the one from the parent. if (!descriptors.containsKey(parentDescriptor.getRoleHint())) { descriptors.put(parentDescriptor.getRoleHint(), parentDescriptor); } } } return new ArrayList<ComponentDescriptor<T>>(descriptors.values()); } @Override public ComponentEventManager getComponentEventManager() { return this.eventManager; } @Override public void setComponentEventManager(ComponentEventManager eventManager) { this.eventManager = eventManager; } @Override public ComponentManager getParent() { return this.parent; } @Override public void setParent(ComponentManager parentComponentManager) { this.parent = parentComponentManager; } private <T> T createInstance(ComponentDescriptor<T> descriptor) throws Exception { T instance = descriptor.getImplementation().newInstance(); // Set each dependency for (ComponentDependency<?> dependency : descriptor.getComponentDependencies()) { // TODO: Handle dependency cycles // Handle different field types Object fieldValue; // Step 1: Verify if there's a Provider registered for the field type // - A Provider is a component like any other (except it cannot have a field produced by itself!) // - A Provider must implement the JSR330 Producer interface // // Step 2: Handle Logger injection. // // Step 3: No producer found, handle scalar and collection types by looking up standard component // implementations. Class<?> dependencyRoleClass = ReflectionUtils.getTypeClass(dependency.getRoleType()); if (dependencyRoleClass.isAssignableFrom(Logger.class)) { fieldValue = createLogger(instance.getClass()); } else if (dependencyRoleClass.isAssignableFrom(List.class)) { fieldValue = getInstanceList(ReflectionUtils.getLastTypeGenericArgument(dependency.getRoleType())); } else if (dependencyRoleClass.isAssignableFrom(Map.class)) { fieldValue = getInstanceMap(ReflectionUtils.getLastTypeGenericArgument(dependency.getRoleType())); } else if (dependencyRoleClass.isAssignableFrom(Provider.class)) { // Check if there's a Provider registered for the type if (hasComponent(dependency.getRoleType(), dependency.getRoleHint())) { fieldValue = getInstance(dependency.getRoleType(), dependency.getRoleHint()); } else { fieldValue = new GenericProvider<Object>(this, new RoleHint<Object>( ReflectionUtils.getLastTypeGenericArgument(dependency.getRoleType()), dependency.getRoleHint())); } } else { fieldValue = getInstance(dependency.getRoleType(), dependency.getRoleHint()); } // Set the field by introspection if (fieldValue != null) { ReflectionUtils.setFieldValue(instance, dependency.getName(), fieldValue); } } // Call Lifecycle Handlers for (LifecycleHandler lifecycleHandler : this.lifecycleHandlers) { lifecycleHandler.handle(instance, descriptor, this); } return instance; } /** * Create a Logger instance to inject. */ protected Object createLogger(Class<?> instanceClass) { return LoggerFactory.getLogger(instanceClass); } @SuppressWarnings("unchecked") protected <T> T getComponentInstance(RoleHint<T> roleHint) throws ComponentLookupException { T instance; ComponentEntry<T> componentEntry = (ComponentEntry<T>) this.componentEntries.get(roleHint); if (componentEntry != null) { try { instance = getComponentInstance(componentEntry); } catch (Throwable e) { throw new ComponentLookupException( String.format("Failed to lookup component [%s] identified by [%s]", componentEntry.descriptor.getImplementation().getName(), roleHint.toString()), e); } } else { if (getParent() != null) { instance = getParent().getInstance(roleHint.getRoleType(), roleHint.getHint()); } else { throw new ComponentLookupException("Can't find descriptor for the component [" + roleHint + "]"); } } return instance; } private <T> T getComponentInstance(ComponentEntry<T> componentEntry) throws Exception { T instance; ComponentDescriptor<T> descriptor = componentEntry.descriptor; if (descriptor.getInstantiationStrategy() == ComponentInstantiationStrategy.SINGLETON) { if (componentEntry.instance != null) { // If the instance exists return it instance = componentEntry.instance; } else { synchronized (componentEntry) { // Recheck in case it has been created while we were waiting if (componentEntry.instance != null) { instance = componentEntry.instance; } else { componentEntry.instance = createInstance(descriptor); instance = componentEntry.instance; } } } } else { instance = createInstance(descriptor); } return instance; } // Add private <T> RoleHint<T> getRoleHint(ComponentDescriptor<T> componentDescriptor) { return new RoleHint<T>(componentDescriptor.getRoleType(), componentDescriptor.getRoleHint()); } @Override public <T> void registerComponent(ComponentDescriptor<T> componentDescriptor) throws ComponentRepositoryException { registerComponent(componentDescriptor, null); } @Override public <T> void registerComponent(ComponentDescriptor<T> componentDescriptor, T componentInstance) { RoleHint<T> roleHint = getRoleHint(componentDescriptor); // Remove any existing component associated to the provided roleHint removeComponentWithoutException(roleHint); // Register new component addComponent(roleHint, new DefaultComponentDescriptor<T>(componentDescriptor), componentInstance); } private <T> void addComponent(RoleHint<T> roleHint, ComponentDescriptor<T> descriptor, T instance) { ComponentEntry<T> componentEntry = new ComponentEntry<T>(descriptor, instance); // Register new component this.componentEntries.put(roleHint, componentEntry); // Send event about component registration if (this.eventManager != null) { this.eventManager.notifyComponentRegistered(descriptor, this); } } // Remove @Override public void unregisterComponent(Type role, String hint) { removeComponentWithoutException(new RoleHint<Object>(role, hint)); } @Override public void unregisterComponent(ComponentDescriptor<?> componentDescriptor) { if (ObjectUtils.equals( getComponentDescriptor(componentDescriptor.getRoleType(), componentDescriptor.getRoleHint()), componentDescriptor)) { unregisterComponent(componentDescriptor.getRoleType(), componentDescriptor.getRoleHint()); } } @Override @SuppressWarnings("unchecked") public void release(Object component) throws ComponentLifecycleException { // First find the descriptor matching the passed component RoleHint<?> key = null; ComponentDescriptor<?> oldDescriptor = null; for (Map.Entry<RoleHint<?>, ComponentEntry<?>> entry : this.componentEntries.entrySet()) { if (entry.getValue().instance == component) { key = entry.getKey(); oldDescriptor = entry.getValue().descriptor; break; } } // Note that we're not removing inside the for loop above since it would cause a Concurrent // exception since we'd modify the map accessed by the iterator. if (key != null) { // We do the following: // - fire an unregistration event, to tell the world that this reference is now dead // - fire a registration event, to tell the world that it could get a new reference for this component // now // We need to do this since code holding a reference on the released component may need to know it's // been removed and thus discard its own reference to that component and look it up again. // Another solution would be to introduce a new event for Component creation/destruction (right now // we only send events for Component registration/unregistration). removeComponent(key); addComponent((RoleHint<Object>) key, (ComponentDescriptor<Object>) oldDescriptor, null); } } private void releaseInstance(ComponentEntry<?> componentEntry) throws ComponentLifecycleException { // Make sure the singleton component instance can't be "lost" (impossible to dispose because returned but not // stored). synchronized (componentEntry) { Object instance = componentEntry.instance; // Give a chance to the component to clean up if (instance instanceof Disposable) { ((Disposable) instance).dispose(); } componentEntry.instance = null; } } private void releaseComponentEntry(ComponentEntry<?> componentEntry) throws ComponentLifecycleException { // clean existing instance releaseInstance(componentEntry); } private void removeComponent(RoleHint<?> roleHint) throws ComponentLifecycleException { // Make sure to remove the entry from the map before destroying it to reduce at the minimum the risk of // lookupping something invalid ComponentEntry<?> componentEntry = this.componentEntries.remove(roleHint); if (componentEntry != null) { ComponentDescriptor<?> oldDescriptor = componentEntry.descriptor; // We don't want the component manager to dispose itself just because it's not registered as component* // anymore if (componentEntry.instance != this) { // clean any resource associated to the component instance and descriptor releaseComponentEntry(componentEntry); } // Send event about component unregistration if (this.eventManager != null && oldDescriptor != null) { this.eventManager.notifyComponentUnregistered(oldDescriptor, this); } } } /** * Note: This method shouldn't exist but register/unregister methods should throw a * {@link ComponentLifecycleException} but that would break backward compatibility to add it. */ private <T> void removeComponentWithoutException(RoleHint<T> roleHint) { try { removeComponent(roleHint); } catch (Exception e) { logger.warn("Instance released but disposal failed. Some resources may not have been released.", e); } } private int sortEntry(List<RoleHint<?>> keys, int index) { int oldIndex = index; int newIndex = index; RoleHint<?> key = keys.get(index); ComponentEntry<?> componentEntry = this.componentEntries.get(key); for (ComponentDependency<?> dependency : componentEntry.descriptor.getComponentDependencies()) { RoleHint<?> dependencyRole = new RoleHint<Object>(dependency.getRoleType(), dependency.getRoleHint()); int dependencyIndex = keys.indexOf(dependencyRole); if (dependencyIndex != -1 && dependencyIndex < newIndex) { dependencyIndex = sortEntry(keys, dependencyIndex); newIndex = dependencyIndex; } } if (newIndex != oldIndex) { key = keys.remove(oldIndex); keys.add(newIndex, key); } return newIndex; } @Override public void dispose() { List<RoleHint<?>> keys = new ArrayList<RoleHint<?>>(this.componentEntries.keySet()); // Exclude this component RoleHint<ComponentManager> cmRoleHint = new RoleHint<ComponentManager>(ComponentManager.class); ComponentEntry<?> cmEntry = this.componentEntries.get(cmRoleHint); if (cmEntry != null && cmEntry.instance == this) { keys.remove(cmRoleHint); } // Order component based on dependencies relations for (int i = 0; i < keys.size(); ++i) { i = sortEntry(keys, i); } // Dispose old components for (RoleHint<?> key : keys) { ComponentEntry<?> componentEntry = this.componentEntries.get(key); synchronized (componentEntry) { Object instance = componentEntry.instance; if (instance instanceof Disposable) { try { ((Disposable) instance).dispose(); } catch (ComponentLifecycleException e) { this.logger.error("Failed to dispose component with role type [{}] and role hint [{}]", componentEntry.descriptor.getRoleType(), componentEntry.descriptor.getRoleHint(), e); } } } } // Remove disposed components from the map. Doing it in two steps to give as many chances as possible to the // components that have to use a component already disposed (usually because it dynamically requires it and // there is no way for the ComponentManager to know that dependency). for (RoleHint<?> key : keys) { this.componentEntries.remove(key); } } // Deprecated @Override @SuppressWarnings("unchecked") @Deprecated public <T> List<ComponentDescriptor<T>> getComponentDescriptorList(Class<T> role) { List<ComponentDescriptor<T>> results = new ArrayList<ComponentDescriptor<T>>(); for (Map.Entry<RoleHint<?>, ComponentEntry<?>> entry : this.componentEntries.entrySet()) { if (entry.getKey().getRoleClass() == role) { results.add((ComponentDescriptor<T>) entry.getValue().descriptor); } } return results; } }