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}