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 java.util.Collection;
019import java.util.List;
020import java.util.Map;
021
022import org.apache.commons.lang.StringUtils;
023import org.kuali.rice.krad.data.metadata.DataObjectAttribute;
024import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
025import org.kuali.rice.krad.data.metadata.MetadataMergeAction;
026import org.kuali.rice.krad.data.metadata.impl.DataObjectAttributeInternal;
027import org.kuali.rice.krad.data.metadata.impl.DataObjectMetadataInternal;
028import org.kuali.rice.krad.data.provider.CompositeMetadataProvider;
029import org.kuali.rice.krad.data.provider.MetadataProvider;
030
031/**
032 * This "provider" aggregates the other metadata providers given in its spring configuration.
033 *
034 * <p>
035 * The providers are processed in order, each one having the option to overlay information provided by earlier providers
036 * in the chain. The nature of the merge/overlay depends on the value of the mergeAction property on the returned
037 * object.
038 * </p>
039 * 
040 * @see MetadataMergeAction
041 * 
042 * @author Kuali Rice Team (rice.collab@kuali.org)
043 */
044public class CompositeMetadataProviderImpl extends MetadataProviderBase implements CompositeMetadataProvider {
045        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
046                        .getLogger(CompositeMetadataProviderImpl.class);
047
048        protected List<MetadataProvider> providers;
049
050    /**
051     * {@inheritDoc}
052     */
053        @Override
054        protected synchronized void initializeMetadata(Collection<Class<?>> types) {
055                if (LOG.isInfoEnabled()) {
056                        LOG.info("Initializing Metadata from sources: " + providers);
057                }
058                masterMetadataMap.clear();
059                if (!providers.isEmpty()) {
060                        // the first one is the master list - later providers will be merged in by embedding the earlier processed
061                        // items
062                        // into the later ones.
063                        // This allows the later providers to be "less complete", only setting the values they want/need to
064                        // override.
065                        for (MetadataProvider provider : providers) {
066                                if (LOG.isInfoEnabled()) {
067                                        LOG.info(" *** Processing MetadataProvider: " + provider);
068                                }
069                                // Obtain the data from the next provider
070                                // If the provider requires us to provide it the types discovered so far, then pull that
071                                // from the keys of the map
072                                Map<Class<?>, DataObjectMetadata> metadata = null;
073                                if (provider.requiresListOfExistingTypes()) {
074                                        metadata = provider.provideMetadataForTypes(masterMetadataMap.keySet());
075                                } else {
076                                        metadata = provider.provideMetadata();
077                                }
078                                // for these, we need to, if the objects are already in the master map, not replace it, but wrap the
079                                // existing object in the map with the one from the next provider
080                                for (Class<?> dataObjectType : metadata.keySet()) {
081                                        DataObjectMetadata existingMetadata = masterMetadataMap.get(dataObjectType);
082                                        DataObjectMetadata newMetadata = metadata.get(dataObjectType);
083                                        mergeMetadataForType(newMetadata, existingMetadata);
084                                }
085                        }
086                        // Now that all the data has been merged, go through the data object attributes to look for inherited
087                        // properties
088                        // and merge those attributes appropriately
089                        // This can not be done as part of the "normal" merge process as the needed attributes may not exist
090                        // until all the providers have been processed.
091                        mergeInheritedAttributes();
092                }
093        }
094
095    /**
096     * Merges attributes from the current map with those that are inherited.
097     */
098        protected void mergeInheritedAttributes() {
099                for (DataObjectMetadata metadata : masterMetadataMap.values()) {
100                        for (DataObjectAttribute attr : metadata.getAttributes()) {
101                                if (attr.isInherited()) {
102                                        if (LOG.isDebugEnabled()) {
103                                                LOG.debug("Processing inherited attribute on " + metadata.getType() + "." + attr.getName()
104                                                                + " : " + attr.getInheritedFromType() + " / "
105                                                                + attr.getInheritedFromParentAttributeName() + "."
106                                                                + attr.getInheritedFromAttributeName());
107                                        }
108                                        // now that we know there is a cross-data object inheritance, we pull the attribute with the
109                                        // inheritance definition (Which will never have an embedded attribute at this point, since
110                                        // they are created without one by the Annotation provider.)
111                                        DataObjectAttribute originalDataObjectAttribute = attr.getOriginalDataObjectAttribute();
112                                        if (originalDataObjectAttribute == null) {
113                                                // sanity check - just in case someone misconfigured this (via spring provider)
114                                                LOG.error("originalDataObjectAttribute was null for " + attr);
115                                                continue;
116                                        }
117                                        // we need something which allows embedding
118                                        if (!(originalDataObjectAttribute instanceof DataObjectAttributeInternal)) {
119                                                LOG.warn("The originalDataObjectAttribute does not implement the DataObjectAttributeInternal interface, we have no access to the embeddedAttribute property: "
120                                                                + originalDataObjectAttribute);
121                                        }
122                                        Class<?> inheritedFromType = originalDataObjectAttribute.getInheritedFromType();
123                                        String inheritedFromAttributeName = originalDataObjectAttribute.getInheritedFromAttributeName();
124                                        if (inheritedFromType == null || StringUtils.isBlank(inheritedFromAttributeName)) {
125                                                // another sanity check
126                                                LOG.error("inheritedFromType/inheritedFromAttributeName not completely populated for "
127                                                                + originalDataObjectAttribute);
128                                                continue;
129                                        }
130                                        // Now, attempt to find the data object type in the map
131                                        DataObjectMetadata inheritedMetadata = masterMetadataMap.get(inheritedFromType);
132                                        if (inheritedMetadata == null) {
133                                                // again - it may not exist (since linked by class name) - so handle that gracefully with a
134                                                // warning
135                                                LOG.warn("The metadata object for the inheritance does not exist, skipping: "
136                                                                + inheritedFromType);
137                                                continue;
138                                        }
139                                        DataObjectAttribute inheritedAttribute = inheritedMetadata.getAttribute(inheritedFromAttributeName);
140                                        if (inheritedAttribute == null) {
141                                                // Really - we should have blown up before this, since the linker would have failed
142                                                LOG.warn("The attribute on the metadata object for the inheritance does not exist, skipping: "
143                                                                + inheritedFromType + "." + inheritedFromAttributeName);
144                                                continue;
145                                        }
146                                        // Finally - we have the data we need - MERGE IT!
147                                        ((DataObjectAttributeInternal) originalDataObjectAttribute)
148                                                        .setEmbeddedAttribute(inheritedAttribute);
149                                }
150                        }
151                }
152        }
153
154    /**
155     * Merges the metadata of two specific types.
156     *
157     * @param newMetadata the metadata to merge in.
158     * @param existingMetadata the existing metadata to merge into.
159     */
160        protected void mergeMetadataForType(DataObjectMetadata newMetadata, DataObjectMetadata existingMetadata) {
161                if (LOG.isDebugEnabled()) {
162                        LOG.debug("Type: " + newMetadata.getType() + " : " + newMetadata);
163                }
164                // get the object from the existing map
165                // if not there, just set the new value and continue
166                if (existingMetadata == null) {
167                        if (newMetadata.getMergeAction() != MetadataMergeAction.REMOVE) {
168                                LOG.debug("New Type - Adding metadata to masterMetadataMap");
169                                masterMetadataMap.put(newMetadata.getType(), newMetadata);
170                        } else {
171                                // If the merge action states to remove, then it's (likely) an incomplete definition and we
172                                // don't want to add it
173                                // This would happen if the definition was removed from an earlier metadata provider but the
174                                // override to
175                                // remove it is still present.
176                                LOG.warn("Attempt to REMOVE a DataObjectMetadata which did not exist: " + newMetadata.getType());
177                        }
178                } else {
179                        if (LOG.isDebugEnabled()) {
180                                LOG.debug("Type already exists.  Merging previously retrieved metadata using action "
181                                                + newMetadata.getMergeAction() + " : " + newMetadata.getType());
182                        }
183                        if (newMetadata.getMergeAction() == MetadataMergeAction.MERGE) {
184                                // if there, replace with the object returned from the new map and set the prior one as
185                                // embedded
186                                // the embedding logic is only on this internal interface
187                                if (newMetadata instanceof DataObjectMetadataInternal
188                                                && existingMetadata instanceof DataObjectMetadataInternal) {
189                                        ((DataObjectMetadataInternal) newMetadata)
190                                                        .setEmbedded((DataObjectMetadataInternal) existingMetadata);
191                                        masterMetadataMap.put(newMetadata.getType(), newMetadata);
192                                } else {
193                                        LOG.warn("New or existing Metadata object does not implement the DataObjectMetadataInternal interface, unable to embed the previously retrieved metadata.  REPLACING the entry in the masterMetadataMap ("
194                                                        + existingMetadata + ") with the new version: " + newMetadata);
195                                        masterMetadataMap.put(newMetadata.getType(), newMetadata);
196                                }
197                        } else if (newMetadata.getMergeAction() == MetadataMergeAction.REPLACE) {
198                                // use the local metadata and do not embed
199                                masterMetadataMap.put(newMetadata.getType(), newMetadata);
200                        } else if (newMetadata.getMergeAction() == MetadataMergeAction.REMOVE) {
201                                masterMetadataMap.remove(newMetadata.getType());
202                        } else if (newMetadata.getMergeAction() == MetadataMergeAction.NO_OVERRIDE) {
203                                // Do nothing, leave the original in the map
204
205                        } else {
206                                LOG.warn("Unsupported MetadataMergeAction: " + newMetadata.getMergeAction() + " on " + newMetadata);
207                        }
208                }
209
210        }
211
212    /**
213     * {@inheritDoc}
214     */
215        @Override
216        public List<MetadataProvider> getProviders() {
217                return providers;
218        }
219
220    /**
221     * Setter for the providers.
222     *
223     * @param providers the providers to set.
224     */
225        public void setProviders(List<MetadataProvider> providers) {
226                this.providers = providers;
227        }
228
229}