/** * $Id: EntityProviderManagerImpl.java 113499 2012-09-25 01:13:56Z azeckoski@unicon.net $ * $URL: https://source.sakaiproject.org/svn/entitybroker/trunk/impl/src/java/org/sakaiproject/entitybroker/impl/entityprovider/EntityProviderManagerImpl.java $ * EBlogic.java - entity-broker - Apr 15, 2008 4:29:18 PM - azeckoski ************************************************************************** * Copyright (c) 2007, 2008, 2009 The Sakai Foundation * * Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.entitybroker.impl.entityprovider; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.Map.Entry; import org.azeckoski.reflectutils.ReflectUtils; import org.azeckoski.reflectutils.refmap.ReferenceMap; import org.azeckoski.reflectutils.refmap.ReferenceType; import org.sakaiproject.entitybroker.EntityReference; import org.sakaiproject.entitybroker.entityprovider.CoreEntityProvider; import org.sakaiproject.entitybroker.entityprovider.EntityProvider; import org.sakaiproject.entitybroker.entityprovider.EntityProviderManager; import org.sakaiproject.entitybroker.entityprovider.EntityProviderMethodStore; import org.sakaiproject.entitybroker.entityprovider.capabilities.ActionsDefineable; import org.sakaiproject.entitybroker.entityprovider.capabilities.ActionsExecutable; import org.sakaiproject.entitybroker.entityprovider.capabilities.ActionsExecutionControllable; import org.sakaiproject.entitybroker.entityprovider.capabilities.DescribePropertiesable; import org.sakaiproject.entitybroker.entityprovider.capabilities.Describeable; import org.sakaiproject.entitybroker.entityprovider.capabilities.RedirectControllable; import org.sakaiproject.entitybroker.entityprovider.capabilities.RedirectDefinable; import org.sakaiproject.entitybroker.entityprovider.capabilities.Redirectable; import org.sakaiproject.entitybroker.entityprovider.capabilities.RequestAware; import org.sakaiproject.entitybroker.entityprovider.capabilities.RequestStorable; import org.sakaiproject.entitybroker.entityprovider.extension.CustomAction; import org.sakaiproject.entitybroker.entityprovider.extension.EntityProviderListener; import org.sakaiproject.entitybroker.entityprovider.extension.RequestGetterWrite; import org.sakaiproject.entitybroker.entityprovider.extension.RequestStorageWrite; import org.sakaiproject.entitybroker.entityprovider.extension.URLRedirect; import org.sakaiproject.entitybroker.providers.EntityPropertiesService; import org.sakaiproject.entitybroker.providers.EntityRequestHandler; import org.sakaiproject.entitybroker.util.core.EntityProviderMethodStoreImpl; /** * Base implementation of the entity provider manager * * @author Aaron Zeckoski (aaronz@vt.edu) * @author Antranig Basman (antranig@caret.cam.ac.uk) */ public class EntityProviderManagerImpl implements EntityProviderManager { public void init() { System.out.println("EntityProviderManagerImpl init"); // register the describe prefix to reserve them registerEntityProvider( new EntityProvider() { public String getEntityPrefix() { return EntityRequestHandler.DESCRIBE; } } ); } /** * Empty constructor */ protected EntityProviderManagerImpl() { } /** * Base constructor * @param requestStorage the request storage service (writeable) * @param requestGetter the request getter service * @param entityProperties the entity properties service * @param entityProviderMethodStore the provider method storage */ public EntityProviderManagerImpl(RequestStorageWrite requestStorage, RequestGetterWrite requestGetter, EntityPropertiesService entityProperties, EntityProviderMethodStore entityProviderMethodStore) { super(); this.requestStorage = requestStorage; this.requestGetter = requestGetter; this.entityProperties = entityProperties; this.entityProviderMethodStore = entityProviderMethodStore; init(); } private RequestStorageWrite requestStorage; private RequestGetterWrite requestGetter; private EntityPropertiesService entityProperties; private EntityProviderMethodStore entityProviderMethodStore; protected ReferenceMap<String, EntityProvider> prefixMap = new ReferenceMap<String, EntityProvider>(ReferenceType.STRONG, ReferenceType.SOFT); @SuppressWarnings("unchecked") protected ReferenceMap<String, EntityProviderListener> listenerMap = new ReferenceMap<String, EntityProviderListener>(ReferenceType.STRONG, ReferenceType.SOFT); // old CHMs were switched to RMs to avoid holding strong references and allowing clean classloader unloads // protected ConcurrentMap<String, EntityProvider> prefixMap = new ConcurrentHashMap<String, EntityProvider>(); // protected ConcurrentMap<String, ReferenceParseable> parseMap = new ConcurrentHashMap<String, ReferenceParseable>(); /* (non-Javadoc) * @see org.sakaiproject.entitybroker.managers.EntityProviderManager#getProviderByPrefix(java.lang.String) */ public EntityProvider getProviderByPrefix(String prefix) { EntityProvider provider = getProviderByPrefixAndCapability(prefix, CoreEntityProvider.class); if (provider == null) { provider = getProviderByPrefixAndCapability(prefix, EntityProvider.class); } return provider; } /* (non-Javadoc) * @see org.sakaiproject.entitybroker.entityprovider.EntityProviderManager#getProviderByPrefixAndCapability(java.lang.String, java.lang.Class) */ @SuppressWarnings("unchecked") public <T extends EntityProvider> T getProviderByPrefixAndCapability(String prefix, Class<T> capability) { T provider = null; if (capability == null) { throw new NullPointerException("capability cannot be null"); } String bikey = getBiKey(prefix, capability); provider = (T) prefixMap.get(bikey); return provider; } /* * (non-Javadoc) * @see org.sakaiproject.entitybroker.entityprovider.EntityProviderManager#getRegisteredPrefixes() */ public Set<String> getRegisteredPrefixes() { Set<String> togo = new HashSet<String>(); for (String bikey : prefixMap.keySet()) { togo.add(getPrefix(bikey)); } return togo; } /* (non-Javadoc) * @see org.sakaiproject.entitybroker.entityprovider.EntityProviderManager#getPrefixCapabilities(java.lang.String) */ public List<Class<? extends EntityProvider>> getPrefixCapabilities(String prefix) { List<Class<? extends EntityProvider>> caps = new ArrayList<Class<? extends EntityProvider>>(); for (String bikey : prefixMap.keySet()) { String curPrefix = EntityProviderManagerImpl.getPrefix(bikey); if (curPrefix.equals(prefix)) { try { Class<? extends EntityProvider> capability = getCapability(bikey); caps.add( capability ); } catch (RuntimeException e) { // added because there will be times where we cannot resolve capabilities // because of shifting ClassLoaders or CL visibility and that should not cause this to die System.out.println("WARN getPrefixCapabilities: Unable to retrieve class for capability bikey ("+bikey+"), skipping this capability"); } } } Collections.sort(caps, new ClassComparator()); return caps; } /* (non-Javadoc) * @see org.sakaiproject.entitybroker.entityprovider.EntityProviderManager#getRegisteredEntityCapabilities() */ public Map<String, List<Class<? extends EntityProvider>>> getRegisteredEntityCapabilities() { Map<String, List<Class<? extends EntityProvider>>> m = new HashMap<String, List<Class<? extends EntityProvider>>>(); ArrayList<String> list = new ArrayList<String>( prefixMap.keySet() ); Collections.sort(list); for (String bikey : list) { String prefix = getPrefix(bikey); if (! m.containsKey(prefix)) { m.put(prefix, new ArrayList<Class<? extends EntityProvider>>()); } try { Class<? extends EntityProvider> capability = getCapability(bikey); m.get(prefix).add( capability ); } catch (RuntimeException e) { // added because there will be times where we cannot resolve capabilities // because of shifting ClassLoaders or CL visibility and that should not cause this to die System.out.println("WARN getRegisteredEntityCapabilities: Unable to retrieve class for capability bikey ("+bikey+"), skipping this capability"); } } return m; } /* (non-Javadoc) * @see org.sakaiproject.entitybroker.entityprovider.EntityProviderManager#getProvidersByCapability(java.lang.Class) */ @SuppressWarnings("unchecked") public <T extends EntityProvider> List<T> getProvidersByCapability(Class<T> capability) { ArrayList<T> providers = new ArrayList<T>(); String capName = capability.getName(); for (Entry<String, EntityProvider> entry : prefixMap.entrySet()) { String name = EntityProviderManagerImpl.getCapabilityName(entry.getKey()); if (capName.equals(name)) { providers.add((T)entry.getValue()); } } Collections.sort(providers, new EntityProviderComparator()); return providers; } /* (non-Javadoc) * @see org.sakaiproject.entitybroker.entityprovider.EntityProviderManager#getPrefixesByCapability(java.lang.Class) */ public <T extends EntityProvider> List<String> getPrefixesByCapability(Class<T> capability) { ArrayList<String> prefixes = new ArrayList<String>(); String capName = capability.getName(); for (Entry<String, EntityProvider> entry : prefixMap.entrySet()) { String name = EntityProviderManagerImpl.getCapabilityName(entry.getKey()); if (capName.equals(name)) { prefixes.add( EntityProviderManagerImpl.getPrefix(entry.getKey()) ); } } Collections.sort(prefixes); return prefixes; } /* * (non-Javadoc) * @see org.sakaiproject.entitybroker.entityprovider.EntityProviderManager#registerEntityProvider(org.sakaiproject.entitybroker.entityprovider.EntityProvider) */ @SuppressWarnings("unchecked") public void registerEntityProvider(EntityProvider entityProvider) { if (entityProvider == null) { throw new IllegalArgumentException("entityProvider cannot be null"); } String prefix = new String( entityProvider.getEntityPrefix() ); // copy to make sure this string is in the EB ClassLoader new EntityReference(prefix, ""); // this checks the prefix is valid // we are now registering describe ourselves // if (EntityRequestHandler.DESCRIBE.equals(prefix)) { // throw new IllegalArgumentException(EntityRequestHandler.DESCRIBE + " is a reserved prefix, it cannot be used"); // } List<Class<? extends EntityProvider>> superclasses = extractCapabilities(entityProvider); int count = 0; for (Class<? extends EntityProvider> superclazz : superclasses) { registerPrefixCapability(prefix, superclazz, entityProvider); count++; // special handling for certain EPs if needed if (superclazz.equals(RequestAware.class)) { // need to shove in the requestGetter on registration ((RequestAware)entityProvider).setRequestGetter(requestGetter); } else if (superclazz.equals(RequestStorable.class)) { // need to shove in the request storage on registration ((RequestStorable)entityProvider).setRequestStorage(requestStorage); } else if (superclazz.equals(ActionsExecutable.class)) { // register the custom actions CustomAction[] customActions = new CustomAction[0]; if ( superclasses.contains(ActionsExecutionControllable.class) || superclasses.contains(ActionsDefineable.class) ) { customActions = ((ActionsDefineable)entityProvider).defineActions(); if (customActions == null) { throw new IllegalArgumentException("ActionsExecutable: defineActions returns null, " + "it must return an array of custom actions (or you can use ActionsExecutable)"); } if (!superclasses.contains(ActionsExecutionControllable.class)) { // do the actions defineable validation check EntityProviderMethodStoreImpl.validateCustomActionMethods((ActionsDefineable)entityProvider); } } else { // auto detect the custom actions customActions = entityProviderMethodStore.findCustomActions(entityProvider, true); } // register the actions Map<String,CustomAction> actions = new HashMap<String, CustomAction>(); for (CustomAction customAction : customActions) { String action = customAction.action; if (action == null || "".equals(action) || EntityRequestHandler.DESCRIBE.equals(action)) { throw new IllegalStateException("action keys cannot be null, '', or " +EntityRequestHandler.DESCRIBE+", invalid custom action defined in defineActions"); } actions.put(action, customAction); } entityProviderMethodStore.setCustomActions(prefix, actions); } else if (superclazz.equals(Describeable.class)) { // need to load up the default properties into the cache if (! superclasses.contains(DescribePropertiesable.class)) { // only load if the props are not defined elsewhere ClassLoader cl = entityProvider.getClass().getClassLoader(); entityProperties.loadProperties(prefix, null, cl); } } else if (superclazz.equals(DescribePropertiesable.class)) { // load up the properties from the provided CL and basename ClassLoader cl = ((DescribePropertiesable)entityProvider).getResourceClassLoader(); String baseName = ((DescribePropertiesable)entityProvider).getBaseName(); entityProperties.loadProperties(prefix, baseName, cl); } else if (superclazz.equals(Redirectable.class)) { URLRedirect[] redirects = entityProviderMethodStore.findURLRedirectMethods(entityProvider); entityProviderMethodStore.addURLRedirects(prefix, redirects); } else if (superclazz.equals(RedirectDefinable.class)) { URLRedirect[] redirects = EntityProviderMethodStoreImpl.validateDefineableTemplates((RedirectDefinable)entityProvider); entityProviderMethodStore.addURLRedirects(prefix, redirects); } else if (superclazz.equals(RedirectControllable.class)) { URLRedirect[] redirects = EntityProviderMethodStoreImpl.validateControllableTemplates((RedirectControllable)entityProvider); entityProviderMethodStore.addURLRedirects(prefix, redirects); } } System.out.println("INFO Registered entity provider ("+entityProvider.getClass().getName() +") prefix ("+prefix+") with "+count+" capabilities"); // call the registered listeners for (Iterator<EntityProviderListener> iterator = listenerMap.values().iterator(); iterator.hasNext();) { EntityProviderListener<? extends EntityProvider> providerListener = iterator.next(); callListener(providerListener, entityProvider); } } /* * (non-Javadoc) * @see org.sakaiproject.entitybroker.EntityProviderManager#unregisterEntityBroker(org.sakaiproject.entitybroker.EntityProvider) */ public void unregisterEntityProvider(EntityProvider entityProvider) { final String prefix = entityProvider.getEntityPrefix(); if (EntityRequestHandler.DESCRIBE.equals(prefix)) { throw new IllegalArgumentException(EntityRequestHandler.DESCRIBE + " is a reserved prefix, it cannot be unregistered"); } List<Class<? extends EntityProvider>> superclasses = extractCapabilities(entityProvider); int count = 0; for (Class<? extends EntityProvider> capability : superclasses) { // ensure that the root EntityProvider is never absent from the map unless // there is a call to unregisterEntityProviderByPrefix if (EntityProvider.class.equals(capability)) { if (getProviderByPrefixAndCapability(prefix, EntityProvider.class) != null) { // needed to ensure that we always have at LEAST the base level EP for a registered entity registerEntityProvider(new EntityProvider() { public String getEntityPrefix() { return prefix; } }); } } else { unregisterCapability(prefix, capability); count++; } } // clean up the properties cache entityProperties.unloadProperties(prefix); System.out.println("INFO Unregistered entity provider ("+entityProvider.getClass().getName()+") and "+count+" capabilities"); } /* * (non-Javadoc) * @see org.sakaiproject.entitybroker.entityprovider.EntityProviderManager#unregisterEntityProviderCapability(java.lang.String, * java.lang.Class) */ public void unregisterCapability(String prefix, Class<? extends EntityProvider> capability) { if (prefix == null || capability == null) { throw new IllegalArgumentException("prefix and capability cannot be null"); } if (EntityProvider.class.equals(capability)) { throw new IllegalArgumentException( "Cannot separately unregister root EntityProvider capability - use unregisterEntityProviderByPrefix instead"); } String key = getBiKey(prefix, capability); prefixMap.remove(key); // do any cleanup that needs to be done when unregistering if (ActionsExecutable.class.equals(capability)) { // clean up the list of custom actions entityProviderMethodStore.removeCustomActions(prefix); } else if (Describeable.class.isAssignableFrom(capability)) { // clean up properties record entityProperties.unloadProperties(prefix); } else if (Redirectable.class.isAssignableFrom(capability)) { // clean up the redirect URLs record entityProviderMethodStore.removeURLRedirects(prefix); } System.out.println("INFO Unregistered entity provider capability ("+capability.getName()+") for prefix ("+prefix+")"); } /* * (non-Javadoc) * @see org.sakaiproject.entitybroker.EntityProviderManager#unregisterEntityProviderByPrefix(java.lang.String) */ public void unregisterEntityProviderByPrefix(String prefix) { if (prefix == null) { throw new NullPointerException("prefix cannot be null"); } for (String bikey : prefixMap.keySet()) { String keypref = getPrefix(bikey); if (keypref.equals(prefix)) { prefixMap.remove(bikey); } } System.out.println("INFO Unregistered entity prefix ("+prefix+")"); } /** * Allows for easy registration of a prefix and capability * * @param prefix * @param capability * @param provider * @return true if the provider is newly registered, false if it was already registered */ public boolean registerPrefixCapability(String prefix, Class<? extends EntityProvider> capability, EntityProvider entityProvider) { String key = getBiKey(prefix, capability); return prefixMap.put(key, entityProvider) == null; } /** * @deprecated use {@link #getProviderByPrefix(String)} instead */ public EntityProvider getProviderByReference(String reference) { EntityReference ref = new EntityReference(reference); return getProviderByPrefix(ref.prefix); } // LISTENERS @SuppressWarnings("unchecked") public <T extends EntityProvider> void registerListener(EntityProviderListener<T> listener, boolean includeExisting) { if (listener == null) { throw new IllegalArgumentException("listener cannot be null"); } // unregister first unregisterListener(listener); // get the filter values String prefix = listener.getPrefixFilter(); Class<T> capability = listener.getCapabilityFilter(); // make the key ensuring it is unique String key = prefix + ":" + capability + ":" + UUID.randomUUID(); // store the listener listenerMap.put(key, listener); // do the immediate calls if requested if (includeExisting) { if (capability == null && prefix == null) { // all List<T> providers = (List<T>) getProvidersByCapability(EntityProvider.class); for (T provider : providers) { listener.run(provider); } } else if (capability == null) { // get by prefix T provider = (T) getProviderByPrefix(prefix); if (provider != null) { listener.run(provider); } } else if (prefix == null) { // get by capability List<T> l = getProvidersByCapability(capability); for (T provider : l) { listener.run(provider); } } else { // get by prefix and capability T provider = getProviderByPrefixAndCapability(prefix, capability); if (provider != null) { listener.run(provider); } } } } /** * Called from {@link #registerEntityProvider(EntityProvider)} when registering a provider */ @SuppressWarnings("unchecked") private void callListener(EntityProviderListener providerListener, EntityProvider entityProvider) { // get the filter values String prefix = providerListener.getPrefixFilter(); Class<? extends EntityProvider> capability = providerListener.getCapabilityFilter(); // call the listener and give it the provider if (capability == null && prefix == null) { // any providerListener.run(entityProvider); } else if (capability == null) { // by prefix only if (prefix.equals(entityProvider.getEntityPrefix())) { providerListener.run(entityProvider); } } else if (prefix == null) { // by capability only if (capability.isAssignableFrom(entityProvider.getClass())) { providerListener.run(entityProvider); } } else { // by prefix and capability only if (prefix.equals(entityProvider.getEntityPrefix()) && capability.isAssignableFrom(entityProvider.getClass())) { providerListener.run(entityProvider); } } } @SuppressWarnings("unchecked") public <T extends EntityProvider> void unregisterListener(EntityProviderListener<T> listener) { if (listener == null) { throw new IllegalArgumentException("listener cannot be null"); } if (! listenerMap.isEmpty()) { // try to find by the object equality and then remove String key = null; for (Entry<String, EntityProviderListener> entry : listenerMap.entrySet()) { if (listener.equals(entry.getValue())) { key = entry.getKey(); break; } } if (key != null) { listenerMap.remove(key); } } } // STATICS // BIKEY methods protected static String getBiKey(String prefix, Class<? extends EntityProvider> clazz) { return prefix + "/" + clazz.getName(); } protected static String getPrefix(String bikey) { int slashpos = bikey.indexOf('/'); return bikey.substring(0, slashpos); } protected static String getCapabilityName(String bikey) { int slashpos = bikey.indexOf('/'); String className = bikey.substring(slashpos + 1); return className; } @SuppressWarnings("unchecked") protected static Class<? extends EntityProvider> getCapability(String bikey) { int slashpos = bikey.indexOf('/'); String className = bikey.substring(slashpos + 1); Class<?> c; try { c = Class.forName(className); } catch (ClassNotFoundException e) { try { c = Class.forName(className, true, Thread.currentThread().getContextClassLoader()); } catch (ClassNotFoundException e1) { throw new RuntimeException("Could not get Class from classname: " + className, e); } } return (Class<? extends EntityProvider>) c; } // OTHER /** * Get the capabilities implemented by this provider * * @param provider * @return */ @SuppressWarnings("unchecked") protected static List<Class<? extends EntityProvider>> extractCapabilities(EntityProvider provider) { List<Class<?>> superclasses = ReflectUtils.getSuperclasses(provider.getClass()); Set<Class<? extends EntityProvider>> capabilities = new HashSet<Class<? extends EntityProvider>>(); for (Class<?> superclazz : superclasses) { if (superclazz.isInterface() && EntityProvider.class.isAssignableFrom(superclazz)) { capabilities.add((Class<? extends EntityProvider>) superclazz); } } return new ArrayList<Class<? extends EntityProvider>>(capabilities); } public static class EntityProviderComparator implements Comparator<EntityProvider>, Serializable { public final static long serialVersionUID = 1l; public int compare(EntityProvider o1, EntityProvider o2) { return o1.getEntityPrefix().compareTo(o2.getEntityPrefix()); } } public static class ClassComparator implements Comparator<Class<?>>, Serializable { public final static long serialVersionUID = 1l; public int compare(Class<?> o1, Class<?> o2) { return o1.getName().compareTo(o2.getName()); } } // GETTERS and SETTERS /* (non-Javadoc) * @see org.sakaiproject.entitybroker.entityprovider.EntityProviderManager#getRequestStorage() */ public RequestStorageWrite getRequestStorage() { return requestStorage; } public void setRequestStorage(RequestStorageWrite requestStorage) { this.requestStorage = requestStorage; } /* (non-Javadoc) * @see org.sakaiproject.entitybroker.entityprovider.EntityProviderManager#getRequestGetter() */ public RequestGetterWrite getRequestGetter() { return requestGetter; } public void setRequestGetter(RequestGetterWrite requestGetter) { this.requestGetter = requestGetter; } /* (non-Javadoc) * @see org.sakaiproject.entitybroker.entityprovider.EntityProviderManager#getEntityProperties() */ public EntityPropertiesService getEntityProperties() { return entityProperties; } public void setEntityProperties(EntityPropertiesService entityProperties) { this.entityProperties = entityProperties; } /* (non-Javadoc) * @see org.sakaiproject.entitybroker.entityprovider.EntityProviderManager#getEntityProviderMethodStore() */ public EntityProviderMethodStore getEntityProviderMethodStore() { return entityProviderMethodStore; } public void setEntityProviderMethodStore(EntityProviderMethodStore entityProviderMethodStore) { this.entityProviderMethodStore = entityProviderMethodStore; } }