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}