001/** 002 * Copyright 2005-2016 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.data.provider.impl; 017 018import com.google.common.base.Predicate; 019import com.google.common.collect.Iterables; 020import com.google.common.collect.LinkedHashMultimap; 021import com.google.common.collect.Multimap; 022import org.apache.commons.lang.ClassUtils; 023import org.apache.commons.lang.Validate; 024import org.apache.log4j.Logger; 025import org.kuali.rice.krad.data.DataObjectService; 026import org.kuali.rice.krad.data.KradDataServiceLocator; 027import org.kuali.rice.krad.data.provider.MetadataProvider; 028import org.kuali.rice.krad.data.provider.PersistenceProvider; 029import org.kuali.rice.krad.data.provider.Provider; 030import org.kuali.rice.krad.data.provider.ProviderRegistry; 031import org.springframework.aop.framework.Advised; 032import org.springframework.aop.support.AopUtils; 033 034import java.lang.reflect.Method; 035import java.util.ArrayList; 036import java.util.Collection; 037import java.util.Collections; 038import java.util.List; 039 040/** 041 * Defines a basic ProviderRegistry implementation. 042 * 043 * @author Kuali Rice Team (rice.collab@kuali.org) 044 */ 045public class ProviderRegistryImpl implements ProviderRegistry { 046 047 private static final Logger LOG = Logger.getLogger(ProviderRegistry.class); 048 049 private static final String GET_DATA_OBJECT_SERVICE_METHOD_NAME = "getDataObjectService"; 050 private static final String SET_DATA_OBJECT_SERVICE_METHOD_NAME = "setDataObjectService"; 051 052 // Multimap of Provider type -> Provider instance mappings 053 // Since all Providers implement Provider, map doubles as list of all registered Providers 054 // The implementation is a LinkedHashMultimap to enforce the ordering semantic for PersistenceProvider selection 055 private final Multimap<Class<? extends Provider>, Provider> providersByType = LinkedHashMultimap.<Class<? extends Provider>, Provider>create(); 056 057 /** 058 * Enumerates all Provider-derived interfaces in the type hierarchy of the specified Provider class. 059 * 060 * @param provider the Provider class to inspect. 061 * @return all Provider-derived interfaces implemented by the Provider. 062 */ 063 protected Iterable<Class<? extends Provider>> enumerateProviderInterfaces(Provider provider) { 064 List<? extends Class> interfaces = ClassUtils.getAllInterfaces(provider.getClass()); 065 Iterable<? extends Class> providerInterfaces = Iterables.filter(interfaces, new Predicate<Class>() { 066 @Override 067 public boolean apply(Class input) { 068 return Provider.class.isAssignableFrom(input); 069 } 070 }); 071 return (Iterable<Class<? extends Provider>>) providerInterfaces; 072 } 073 074 /** 075 * {@inheritDoc} 076 */ 077 @Override 078 public synchronized void registerProvider(Provider provider) { 079 Validate.notNull(provider, "Provider must be non-null"); 080 081 if (hasDataObjectServiceMethod(provider, GET_DATA_OBJECT_SERVICE_METHOD_NAME, new Class[] { })) { 082 injectDataObjectService(provider); 083 } 084 085 // all providers implement Provider, therefore the Provider.class key will map to the list of 086 // every registered Provider instance 087 for (Class<? extends Provider> providerInterface: enumerateProviderInterfaces(provider)) { 088 providersByType.put(providerInterface, provider); 089 } 090 } 091 092 /** 093 * {@inheritDoc} 094 */ 095 @Override 096 public synchronized boolean unregisterProvider(Provider provider) { 097 Validate.notNull(provider, "Provider must be non-null"); 098 boolean removed = false; 099 Collection<Provider> providers = providersByType.values(); 100 101 // {@link java.util.Collection#remove} semantics for multimap is to remove a *single* occurrence of the given object 102 // so we need to keep removing the provider until all mapped instances have been removed 103 while (providers.remove(provider)) { 104 removed = true; 105 } 106 107 return removed; 108 } 109 110 /** 111 * {@inheritDoc} 112 */ 113 @Override 114 public synchronized List<Provider> getProviders() { 115 return Collections.unmodifiableList(new ArrayList<Provider>(providersByType.get(Provider.class))); 116 } 117 118 /** 119 * {@inheritDoc} 120 */ 121 @Override 122 public synchronized List<Provider> getProvidersForType(Class<? extends Provider> providerType) { 123 Validate.isTrue(providerType != null, "Provider type must be non-null"); 124 return Collections.unmodifiableList(new ArrayList<Provider>(providersByType.get(providerType))); 125 } 126 127 /** 128 * {@inheritDoc} 129 */ 130 @Override 131 public synchronized List<MetadataProvider> getMetadataProviders() { 132 Collection<Provider> metadataProviders = providersByType.get(MetadataProvider.class); 133 return Collections.unmodifiableList(new ArrayList(metadataProviders)); 134 } 135 136 /** 137 * {@inheritDoc} 138 */ 139 @Override 140 public synchronized PersistenceProvider getPersistenceProvider(Class<?> type) { 141 Validate.notNull(type, "Data object type must be non-null"); 142 Collection<Provider> persistenceProviders = providersByType.get(PersistenceProvider.class); 143 // return a single PersistenceProvider that handles the specified type 144 // we just select the first one 145 for (Provider provider: persistenceProviders) { 146 PersistenceProvider persistenceProvider = (PersistenceProvider) provider; 147 if (persistenceProvider.handles(type)) { 148 return persistenceProvider; 149 } 150 } 151 return null; 152 } 153 154 /** 155 * {@inheritDoc} 156 */ 157 @Override 158 public MetadataProvider getMetadataProvider(Class<?> type) { 159 Validate.notNull(type, "Data object type must be non-null"); 160 Collection<MetadataProvider> metadataProviders = getMetadataProviders(); 161 // return a single PersistenceProvider that handles the specified type 162 // we just select the first one 163 for (MetadataProvider provider : metadataProviders) { 164 if (provider.handles(type)) { 165 return provider; 166 } 167 } 168 return null; 169 } 170 171 /** 172 * Determines if the given {@link Provider} has the given method. 173 * 174 * @param provider the {@link org.kuali.rice.krad.data.provider.Provider} to check. 175 * @param methodName the method name to check for. 176 * @param args the arguments for the method. 177 * @return TRUE if the Provider has the given method name, FALSE otherwise. 178 */ 179 protected boolean hasDataObjectServiceMethod(Provider provider, String methodName, Class[] args) { 180 Method methodToFind; 181 182 try { 183 methodToFind = unwrapProxy(provider).getClass().getMethod(methodName, args); 184 } catch (Exception e) { 185 return false; 186 } 187 188 return (methodToFind != null); 189 } 190 191 /** 192 * Returns the object being proxied, otherwise the given object is returned. 193 * 194 * @param bean The proxy to get the underlying object. 195 * @return object being proxied, otherwise the given object is returned. 196 * @throws Exception if errors while getting the underlying object. 197 */ 198 private Object unwrapProxy(Object bean) throws Exception { 199 200 /* 201 * If the given object is a proxy, set the return value as the object 202 * being proxied, otherwise return the given object. 203 */ 204 if (AopUtils.isAopProxy(bean) && bean instanceof Advised) { 205 206 Advised advised = (Advised) bean; 207 208 bean = advised.getTargetSource().getTarget(); 209 } 210 211 return bean; 212 } 213 214 /** 215 * Method attempts to inject a {@link DataObjectService} if the getter method returns null and a setter method 216 * exists. 217 * 218 * @param provider The {@link Provider} to check for getter and setter methods. 219 */ 220 private void injectDataObjectService(Provider provider) { 221 try { 222 Method getterMethod = unwrapProxy(provider).getClass().getMethod(GET_DATA_OBJECT_SERVICE_METHOD_NAME); 223 if (getterMethod.invoke(unwrapProxy(provider)) == null) { 224 if (hasDataObjectServiceMethod(provider, SET_DATA_OBJECT_SERVICE_METHOD_NAME, 225 new Class[] { DataObjectService.class })) { 226 227 Method setterMethod = unwrapProxy(provider).getClass().getMethod( 228 SET_DATA_OBJECT_SERVICE_METHOD_NAME, new Class[]{DataObjectService.class}); 229 setterMethod.invoke(unwrapProxy(provider), KradDataServiceLocator.getDataObjectService()); 230 } 231 } 232 } catch (Exception e) { 233 LOG.warn("Error injecting DataObjectService while registering provider: " + provider.getClass()); 234 } 235 } 236 237}