View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.data.provider.impl;
17  
18  import com.google.common.base.Predicate;
19  import com.google.common.collect.Iterables;
20  import com.google.common.collect.LinkedHashMultimap;
21  import com.google.common.collect.Multimap;
22  import org.apache.commons.lang.ClassUtils;
23  import org.apache.commons.lang.Validate;
24  import org.apache.log4j.Logger;
25  import org.kuali.rice.krad.data.DataObjectService;
26  import org.kuali.rice.krad.data.KradDataServiceLocator;
27  import org.kuali.rice.krad.data.provider.MetadataProvider;
28  import org.kuali.rice.krad.data.provider.PersistenceProvider;
29  import org.kuali.rice.krad.data.provider.Provider;
30  import org.kuali.rice.krad.data.provider.ProviderRegistry;
31  import org.springframework.aop.framework.Advised;
32  import org.springframework.aop.support.AopUtils;
33  
34  import java.lang.reflect.Method;
35  import java.util.ArrayList;
36  import java.util.Collection;
37  import java.util.Collections;
38  import java.util.List;
39  
40  /**
41   * Defines a basic ProviderRegistry implementation.
42   *
43   * @author Kuali Rice Team (rice.collab@kuali.org)
44   */
45  public class ProviderRegistryImpl implements ProviderRegistry {
46  
47      private static final Logger LOG = Logger.getLogger(ProviderRegistry.class);
48  
49      private static final String GET_DATA_OBJECT_SERVICE_METHOD_NAME = "getDataObjectService";
50      private static final String SET_DATA_OBJECT_SERVICE_METHOD_NAME = "setDataObjectService";
51  
52      // Multimap of Provider type -> Provider instance mappings
53      // Since all Providers implement Provider, map doubles as list of all registered Providers
54      // The implementation is a LinkedHashMultimap to enforce the ordering semantic for PersistenceProvider selection
55      private final Multimap<Class<? extends Provider>, Provider> providersByType = LinkedHashMultimap.<Class<? extends Provider>, Provider>create();
56  
57      /**
58       * Enumerates all Provider-derived interfaces in the type hierarchy of the specified Provider class.
59       *
60       * @param provider the Provider class to inspect.
61       * @return all Provider-derived interfaces implemented by the Provider.
62       */
63      protected Iterable<Class<? extends Provider>> enumerateProviderInterfaces(Provider provider) {
64          List<? extends Class> interfaces = ClassUtils.getAllInterfaces(provider.getClass());
65          Iterable<? extends Class> providerInterfaces = Iterables.filter(interfaces, new Predicate<Class>() {
66              @Override
67              public boolean apply(Class input) {
68              return Provider.class.isAssignableFrom(input);
69              }
70          });
71          return (Iterable<Class<? extends Provider>>) providerInterfaces;
72      }
73  
74      /**
75       * {@inheritDoc}
76       */
77      @Override
78      public synchronized void registerProvider(Provider provider) {
79          Validate.notNull(provider, "Provider must be non-null");
80  
81          if (hasDataObjectServiceMethod(provider, GET_DATA_OBJECT_SERVICE_METHOD_NAME, new Class[] { })) {
82              injectDataObjectService(provider);
83          }
84  
85          // all providers implement Provider, therefore the Provider.class key will map to the list of
86          // every registered Provider instance
87          for (Class<? extends Provider> providerInterface: enumerateProviderInterfaces(provider)) {
88              providersByType.put(providerInterface, provider);
89          }
90      }
91  
92      /**
93       * {@inheritDoc}
94       */
95      @Override
96      public synchronized boolean unregisterProvider(Provider provider) {
97          Validate.notNull(provider, "Provider must be non-null");
98          boolean removed = false;
99          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 }