View Javadoc
1   /**
2    * Copyright 2005-2015 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 java.util.Collection;
19  import java.util.List;
20  import java.util.Map;
21  
22  import org.apache.commons.lang.StringUtils;
23  import org.kuali.rice.krad.data.metadata.DataObjectAttribute;
24  import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
25  import org.kuali.rice.krad.data.metadata.MetadataMergeAction;
26  import org.kuali.rice.krad.data.metadata.impl.DataObjectAttributeInternal;
27  import org.kuali.rice.krad.data.metadata.impl.DataObjectMetadataInternal;
28  import org.kuali.rice.krad.data.provider.CompositeMetadataProvider;
29  import org.kuali.rice.krad.data.provider.MetadataProvider;
30  
31  /**
32   * This "provider" aggregates the other metadata providers given in its spring configuration.
33   *
34   * <p>
35   * The providers are processed in order, each one having the option to overlay information provided by earlier providers
36   * in the chain. The nature of the merge/overlay depends on the value of the mergeAction property on the returned
37   * object.
38   * </p>
39   * 
40   * @see MetadataMergeAction
41   * 
42   * @author Kuali Rice Team (rice.collab@kuali.org)
43   */
44  public class CompositeMetadataProviderImpl extends MetadataProviderBase implements CompositeMetadataProvider {
45  	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
46  			.getLogger(CompositeMetadataProviderImpl.class);
47  
48  	protected List<MetadataProvider> providers;
49  
50      /**
51       * {@inheritDoc}
52       */
53  	@Override
54  	protected synchronized void initializeMetadata(Collection<Class<?>> types) {
55  		if (LOG.isInfoEnabled()) {
56  			LOG.info("Initializing Metadata from sources: " + providers);
57  		}
58  		masterMetadataMap.clear();
59  		if (!providers.isEmpty()) {
60  			// the first one is the master list - later providers will be merged in by embedding the earlier processed
61  			// items
62  			// into the later ones.
63  			// This allows the later providers to be "less complete", only setting the values they want/need to
64  			// override.
65  			for (MetadataProvider provider : providers) {
66  				if (LOG.isInfoEnabled()) {
67  					LOG.info(" *** Processing MetadataProvider: " + provider);
68  				}
69  				// Obtain the data from the next provider
70  				// If the provider requires us to provide it the types discovered so far, then pull that
71  				// from the keys of the map
72  				Map<Class<?>, DataObjectMetadata> metadata = null;
73  				if (provider.requiresListOfExistingTypes()) {
74  					metadata = provider.provideMetadataForTypes(masterMetadataMap.keySet());
75  				} else {
76  					metadata = provider.provideMetadata();
77  				}
78  				// for these, we need to, if the objects are already in the master map, not replace it, but wrap the
79  				// existing object in the map with the one from the next provider
80  				for (Class<?> dataObjectType : metadata.keySet()) {
81  					DataObjectMetadata existingMetadata = masterMetadataMap.get(dataObjectType);
82  					DataObjectMetadata newMetadata = metadata.get(dataObjectType);
83  					mergeMetadataForType(newMetadata, existingMetadata);
84  				}
85  			}
86  			// Now that all the data has been merged, go through the data object attributes to look for inherited
87  			// properties
88  			// and merge those attributes appropriately
89  			// This can not be done as part of the "normal" merge process as the needed attributes may not exist
90  			// until all the providers have been processed.
91  			mergeInheritedAttributes();
92  		}
93  	}
94  
95      /**
96       * Merges attributes from the current map with those that are inherited.
97       */
98  	protected void mergeInheritedAttributes() {
99  		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 }