001/** 002 * Copyright 2005-2018 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.datadictionary; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.HashSet; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024 025import org.apache.commons.lang.StringUtils; 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028import org.springframework.beans.factory.support.DefaultListableBeanFactory; 029 030/** 031 * Encapsulates a set of statically generated (typically during startup) 032 * DataDictionary indexes 033 * 034 * @author Kuali Rice Team (rice.collab@kuali.org) 035 */ 036public class DataDictionaryIndex implements Runnable { 037 private static final Log LOG = LogFactory.getLog(DataDictionaryIndex.class); 038 039 private DefaultListableBeanFactory ddBeans; 040 041 // keyed by BusinessObject class 042 private Map<String, BusinessObjectEntry> businessObjectEntries; 043 private Map<String, DataObjectEntry> objectEntries; 044 045 // keyed by documentTypeName 046 private Map<String, DocumentEntry> documentEntries; 047 // keyed by other things 048 private Map<Class, DocumentEntry> documentEntriesByBusinessObjectClass; 049 private Map<Class, DocumentEntry> documentEntriesByMaintainableClass; 050 private Map<String, DataDictionaryEntry> entriesByJstlKey; 051 052 // keyed by a class object, and the value is a set of classes that may block the class represented by the key from inactivation 053 private Map<Class, Set<InactivationBlockingMetadata>> inactivationBlockersForClass; 054 055 private Map<String, List<String>> dictionaryBeansByNamespace; 056 057 public DataDictionaryIndex(DefaultListableBeanFactory ddBeans) { 058 this.ddBeans = ddBeans; 059 060 // primary indices 061 businessObjectEntries = new HashMap<String, BusinessObjectEntry>(); 062 objectEntries = new HashMap<String, DataObjectEntry>(); 063 documentEntries = new HashMap<String, DocumentEntry>(); 064 065 // alternate indices 066 documentEntriesByBusinessObjectClass = new HashMap<Class, DocumentEntry>(); 067 documentEntriesByMaintainableClass = new HashMap<Class, DocumentEntry>(); 068 entriesByJstlKey = new HashMap<String, DataDictionaryEntry>(); 069 070 dictionaryBeansByNamespace = new HashMap<String, List<String>>(); 071 } 072 073 private void buildDDIndicies() { 074 // primary indices 075 businessObjectEntries = new HashMap<String, BusinessObjectEntry>(); 076 objectEntries = new HashMap<String, DataObjectEntry>(); 077 documentEntries = new HashMap<String, DocumentEntry>(); 078 079 // alternate indices 080 documentEntriesByBusinessObjectClass = new HashMap<Class, DocumentEntry>(); 081 documentEntriesByMaintainableClass = new HashMap<Class, DocumentEntry>(); 082 entriesByJstlKey = new HashMap<String, DataDictionaryEntry>(); 083 084 // loop over all beans in the context 085 Map<String, DataObjectEntry> boBeans = ddBeans.getBeansOfType(DataObjectEntry.class); 086 for (DataObjectEntry entry : boBeans.values()) { 087 088 DataObjectEntry indexedEntry = objectEntries.get(entry.getJstlKey()); 089 if (indexedEntry == null) { 090 indexedEntry = businessObjectEntries.get(entry.getJstlKey()); 091 } 092 093 if ((indexedEntry != null) && !(indexedEntry.getDataObjectClass().equals(entry.getDataObjectClass()))) { 094 throw new DataDictionaryException(new StringBuffer( 095 "Two object classes may not share the same jstl key: this=").append(entry.getDataObjectClass()) 096 .append(" / existing=").append(indexedEntry.getDataObjectClass()).toString()); 097 } 098 099 // put all BO and DO entries in the objectEntries map 100 objectEntries.put(entry.getDataObjectClass().getName(), entry); 101 objectEntries.put(entry.getDataObjectClass().getSimpleName(), entry); 102 103 if (entry.getBaseDataObjectClass() != null) { 104 objectEntries.put(entry.getBaseDataObjectClass().getName(), entry); 105 objectEntries.put(entry.getBaseDataObjectClass().getSimpleName(), entry); 106 } 107 108 // keep a separate map of BO entries for now 109 if (entry instanceof BusinessObjectEntry) { 110 BusinessObjectEntry boEntry = (BusinessObjectEntry) entry; 111 112 businessObjectEntries.put(boEntry.getBusinessObjectClass().getName(), boEntry); 113 businessObjectEntries.put(boEntry.getBusinessObjectClass().getSimpleName(), boEntry); 114 115 // If a "base" class is defined for the entry, index the entry by that class as well. 116 if (boEntry.getBaseDataObjectClass() != null) { 117 businessObjectEntries.put(boEntry.getBaseDataObjectClass().getName(), boEntry); 118 businessObjectEntries.put(boEntry.getBaseDataObjectClass().getSimpleName(), boEntry); 119 } 120 } 121 122 entriesByJstlKey.put(entry.getJstlKey(), entry); 123 } 124 125 //Build Document Entry Index 126 Map<String, DocumentEntry> docBeans = ddBeans.getBeansOfType(DocumentEntry.class); 127 for (DocumentEntry entry : docBeans.values()) { 128 String entryName = entry.getDocumentTypeName(); 129 130 if ((entry instanceof TransactionalDocumentEntry) 131 && (documentEntries.get(entry.getFullClassName()) != null) 132 && !StringUtils.equals(documentEntries.get(entry.getFullClassName()).getDocumentTypeName(), 133 entry.getDocumentTypeName())) { 134 throw new DataDictionaryException(new StringBuffer( 135 "Two transactional document types may not share the same document class: this=").append( 136 entry.getDocumentTypeName()).append(" / existing=").append(((DocumentEntry) documentEntries.get( 137 entry.getDocumentClass().getName())).getDocumentTypeName()).toString()); 138 } 139 140 if ((documentEntries.get(entry.getJstlKey()) != null) && !((DocumentEntry) documentEntries.get( 141 entry.getJstlKey())).getDocumentTypeName().equals(entry.getDocumentTypeName())) { 142 throw new DataDictionaryException(new StringBuffer( 143 "Two document types may not share the same jstl key: this=").append(entry.getDocumentTypeName()) 144 .append(" / existing=").append(((DocumentEntry) documentEntries.get(entry.getJstlKey())) 145 .getDocumentTypeName()).toString()); 146 } 147 148 if (entryName != null) { 149 documentEntries.put(entryName, entry); 150 } 151 152 //documentEntries.put(entry.getFullClassName(), entry); 153 documentEntries.put(entry.getDocumentClass().getName(), entry); 154 if (entry.getBaseDocumentClass() != null) { 155 documentEntries.put(entry.getBaseDocumentClass().getName(), entry); 156 } 157 entriesByJstlKey.put(entry.getJstlKey(), entry); 158 159 if (entry instanceof TransactionalDocumentEntry) { 160 TransactionalDocumentEntry tde = (TransactionalDocumentEntry) entry; 161 162 documentEntries.put(tde.getDocumentClass().getSimpleName(), entry); 163 if (tde.getBaseDocumentClass() != null) { 164 documentEntries.put(tde.getBaseDocumentClass().getSimpleName(), entry); 165 } 166 } 167 168 if (entry instanceof MaintenanceDocumentEntry) { 169 MaintenanceDocumentEntry mde = (MaintenanceDocumentEntry) entry; 170 171 documentEntriesByBusinessObjectClass.put(mde.getDataObjectClass(), entry); 172 documentEntriesByMaintainableClass.put(mde.getMaintainableClass(), entry); 173 documentEntries.put(mde.getDataObjectClass().getSimpleName() + "MaintenanceDocument", entry); 174 } 175 } 176 } 177 178 private void buildDDInactivationBlockingIndices() { 179 inactivationBlockersForClass = new HashMap<Class, Set<InactivationBlockingMetadata>>(); 180 181 Map<String, DataObjectEntry> doBeans = ddBeans.getBeansOfType(DataObjectEntry.class); 182 for (DataObjectEntry entry : doBeans.values()) { 183 List<InactivationBlockingDefinition> inactivationBlockingDefinitions = 184 entry.getInactivationBlockingDefinitions(); 185 if (inactivationBlockingDefinitions != null && !inactivationBlockingDefinitions.isEmpty()) { 186 for (InactivationBlockingDefinition inactivationBlockingDefinition : inactivationBlockingDefinitions) { 187 registerInactivationBlockingDefinition(inactivationBlockingDefinition); 188 } 189 } 190 } 191 } 192 193 private void registerInactivationBlockingDefinition(InactivationBlockingDefinition inactivationBlockingDefinition) { 194 Set<InactivationBlockingMetadata> inactivationBlockingDefinitions = inactivationBlockersForClass.get( 195 inactivationBlockingDefinition.getBlockedBusinessObjectClass()); 196 if (inactivationBlockingDefinitions == null) { 197 inactivationBlockingDefinitions = new HashSet<InactivationBlockingMetadata>(); 198 inactivationBlockersForClass.put(inactivationBlockingDefinition.getBlockedBusinessObjectClass(), 199 inactivationBlockingDefinitions); 200 } 201 boolean duplicateAdd = !inactivationBlockingDefinitions.add(inactivationBlockingDefinition); 202 if (duplicateAdd) { 203 throw new DataDictionaryException( 204 "Detected duplicate InactivationBlockingDefinition for class " + inactivationBlockingDefinition 205 .getBlockingReferenceBusinessObjectClass().getClass().getName()); 206 } 207 } 208 209 public void run() { 210 LOG.info("Starting DD Index Building"); 211 buildDDIndicies(); 212 LOG.info("Completed DD Index Building"); 213 214 // LOG.info( "Starting DD Validation" ); 215 // validateDD(); 216 // LOG.info( "Ending DD Validation" ); 217 218 LOG.info("Started DD Inactivation Blocking Index Building"); 219 buildDDInactivationBlockingIndices(); 220 LOG.info("Completed DD Inactivation Blocking Index Building"); 221 } 222 223 public Map<String, BusinessObjectEntry> getBusinessObjectEntries() { 224 return this.businessObjectEntries; 225 } 226 227 public Map<String, DataObjectEntry> getDataObjectEntries() { 228 return this.objectEntries; 229 } 230 231 public Map<String, DocumentEntry> getDocumentEntries() { 232 return this.documentEntries; 233 } 234 235 public Map<Class, DocumentEntry> getDocumentEntriesByBusinessObjectClass() { 236 return this.documentEntriesByBusinessObjectClass; 237 } 238 239 public Map<Class, DocumentEntry> getDocumentEntriesByMaintainableClass() { 240 return this.documentEntriesByMaintainableClass; 241 } 242 243 public Map<String, DataDictionaryEntry> getEntriesByJstlKey() { 244 return this.entriesByJstlKey; 245 } 246 247 public Map<Class, Set<InactivationBlockingMetadata>> getInactivationBlockersForClass() { 248 return this.inactivationBlockersForClass; 249 } 250 251 /** 252 * Mapping of namespace codes to bean definition names that are associated with that namespace 253 * 254 * @return Map<String, List<String>> where map key is namespace code, and map value is list of bean names 255 */ 256 public Map<String, List<String>> getDictionaryBeansByNamespace() { 257 return dictionaryBeansByNamespace; 258 } 259 260 /** 261 * Associates a list of bean names with the given namespace code 262 * 263 * @param namespaceCode - namespace code to associate beans with 264 * @param beanNames - list of bean names that belong to the namespace 265 */ 266 public void addBeanNamesToNamespace(String namespaceCode, List<String> beanNames) { 267 List<String> namespaceBeans = new ArrayList<String>(); 268 if (dictionaryBeansByNamespace.containsKey(namespaceCode)) { 269 namespaceBeans = dictionaryBeansByNamespace.get(namespaceCode); 270 } else { 271 dictionaryBeansByNamespace.put(namespaceCode, namespaceBeans); 272 } 273 namespaceBeans.addAll(beanNames); 274 } 275}