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