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 org.apache.commons.lang.StringUtils;
019import org.apache.commons.logging.Log;
020import org.apache.commons.logging.LogFactory;
021import org.springframework.beans.factory.support.KualiDefaultListableBeanFactory;
022
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
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 KualiDefaultListableBeanFactory 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        public DataDictionaryIndex(KualiDefaultListableBeanFactory ddBeans) {
055                this.ddBeans = ddBeans;
056        }
057
058        public Map<String, BusinessObjectEntry> getBusinessObjectEntries() {
059                return this.businessObjectEntries;
060        }
061
062        public Map<String, DataObjectEntry> getDataObjectEntries() {
063                return this.objectEntries;
064        }
065
066        public Map<String, DocumentEntry> getDocumentEntries() {
067                return this.documentEntries;
068        }
069
070        public Map<Class, DocumentEntry> getDocumentEntriesByBusinessObjectClass() {
071                return this.documentEntriesByBusinessObjectClass;
072        }
073
074        public Map<Class, DocumentEntry> getDocumentEntriesByMaintainableClass() {
075                return this.documentEntriesByMaintainableClass;
076        }
077
078        public Map<String, DataDictionaryEntry> getEntriesByJstlKey() {
079                return this.entriesByJstlKey;
080        }
081
082        public Map<Class, Set<InactivationBlockingMetadata>> getInactivationBlockersForClass() {
083                return this.inactivationBlockersForClass;
084        }
085
086        private void buildDDIndicies() {
087        // primary indices
088        businessObjectEntries = new HashMap<String, BusinessObjectEntry>();
089        objectEntries = new HashMap<String, DataObjectEntry>();
090        documentEntries = new HashMap<String, DocumentEntry>();
091
092        // alternate indices
093        documentEntriesByBusinessObjectClass = new HashMap<Class, DocumentEntry>();
094        documentEntriesByMaintainableClass = new HashMap<Class, DocumentEntry>();
095        entriesByJstlKey = new HashMap<String, DataDictionaryEntry>();
096        
097        // loop over all beans in the context
098        Map<String, DataObjectEntry> boBeans = ddBeans.getBeansOfType(DataObjectEntry.class);
099        for ( DataObjectEntry entry : boBeans.values() ) {
100                
101            DataObjectEntry indexedEntry = objectEntries.get(entry.getJstlKey());
102            if (indexedEntry == null){
103                indexedEntry = businessObjectEntries.get(entry.getJstlKey());
104            }
105                if ( (indexedEntry != null) 
106                    && !(indexedEntry.getDataObjectClass().equals(entry.getDataObjectClass()))) {
107                throw new DataDictionaryException(new StringBuffer("Two object classes may not share the same jstl key: this=").append(entry.getDataObjectClass()).append(" / existing=").append(indexedEntry.getDataObjectClass()).toString());
108            }
109
110                // put all BO and DO entries in the objectEntries map
111                objectEntries.put(entry.getDataObjectClass().getName(), entry);
112            objectEntries.put(entry.getDataObjectClass().getSimpleName(), entry);
113            
114            // keep a separate map of BO entries for now
115                if (entry instanceof BusinessObjectEntry){
116                        BusinessObjectEntry boEntry = (BusinessObjectEntry)entry; 
117                
118                    businessObjectEntries.put(boEntry.getBusinessObjectClass().getName(), boEntry);
119                    businessObjectEntries.put(boEntry.getBusinessObjectClass().getSimpleName(), boEntry);
120                    // If a "base" class is defined for the entry, index the entry by that class as well.
121                    if (boEntry.getBaseBusinessObjectClass() != null) {
122                        businessObjectEntries.put(boEntry.getBaseBusinessObjectClass().getName(), boEntry);
123                        businessObjectEntries.put(boEntry.getBaseBusinessObjectClass().getSimpleName(), boEntry);
124                    }
125                }
126            
127                entriesByJstlKey.put(entry.getJstlKey(), entry);
128        }
129        
130        //Build Document Entry Index
131        Map<String,DocumentEntry> docBeans = ddBeans.getBeansOfType(DocumentEntry.class);
132        for ( DocumentEntry entry : docBeans.values() ) {
133            String entryName = entry.getDocumentTypeName();
134
135            if ((entry instanceof TransactionalDocumentEntry) 
136                    && (documentEntries.get(entry.getFullClassName()) != null) 
137                    && !StringUtils.equals(documentEntries.get(entry.getFullClassName()).getDocumentTypeName(), entry.getDocumentTypeName())) {
138                throw new DataDictionaryException(new StringBuffer("Two transactional document types may not share the same document class: this=")
139                        .append(entry.getDocumentTypeName())
140                        .append(" / existing=")
141                        .append(((DocumentEntry)documentEntries.get(entry.getDocumentClass().getName())).getDocumentTypeName()).toString());
142            }
143            if ((documentEntries.get(entry.getJstlKey()) != null) && !((DocumentEntry)documentEntries.get(entry.getJstlKey())).getDocumentTypeName().equals(entry.getDocumentTypeName())) {
144                throw new DataDictionaryException(new StringBuffer("Two document types may not share the same jstl key: this=").append(entry.getDocumentTypeName()).append(" / existing=").append(((DocumentEntry)documentEntries.get(entry.getJstlKey())).getDocumentTypeName()).toString());
145            }
146
147            if (entryName != null) {
148                documentEntries.put(entryName, entry);
149            }
150            //documentEntries.put(entry.getFullClassName(), entry);
151            documentEntries.put(entry.getDocumentClass().getName(), entry);
152            if (entry.getBaseDocumentClass() != null) {
153                documentEntries.put(entry.getBaseDocumentClass().getName(), entry);
154            }
155            entriesByJstlKey.put(entry.getJstlKey(), entry);
156
157            if (entry instanceof TransactionalDocumentEntry) {
158                TransactionalDocumentEntry tde = (TransactionalDocumentEntry) entry;
159
160                documentEntries.put(tde.getDocumentClass().getSimpleName(), entry);
161                if (tde.getBaseDocumentClass() != null) {
162                        documentEntries.put(tde.getBaseDocumentClass().getSimpleName(), entry);
163                }
164            }
165            if (entry instanceof MaintenanceDocumentEntry) {
166                MaintenanceDocumentEntry mde = (MaintenanceDocumentEntry) entry;
167
168                documentEntriesByBusinessObjectClass.put(mde.getDataObjectClass(), entry);
169                documentEntriesByMaintainableClass.put(mde.getMaintainableClass(), entry);
170                documentEntries.put(mde.getDataObjectClass().getSimpleName() + "MaintenanceDocument", entry);
171            }
172        }
173    }
174
175    private void buildDDInactivationBlockingIndices() {
176        inactivationBlockersForClass = new HashMap<Class, Set<InactivationBlockingMetadata>>();
177        Map<String,DataObjectEntry> doBeans = ddBeans.getBeansOfType(DataObjectEntry.class);
178        for ( DataObjectEntry entry : doBeans.values() ) {
179            List<InactivationBlockingDefinition> inactivationBlockingDefinitions = entry.getInactivationBlockingDefinitions();
180            if (inactivationBlockingDefinitions != null && !inactivationBlockingDefinitions.isEmpty()) {
181                for (InactivationBlockingDefinition inactivationBlockingDefinition : inactivationBlockingDefinitions) {
182                    registerInactivationBlockingDefinition(inactivationBlockingDefinition);
183                }
184            }
185        }
186    }
187    
188    private void registerInactivationBlockingDefinition(InactivationBlockingDefinition inactivationBlockingDefinition) {
189        Set<InactivationBlockingMetadata> inactivationBlockingDefinitions = inactivationBlockersForClass.get(inactivationBlockingDefinition.getBlockedBusinessObjectClass());
190        if (inactivationBlockingDefinitions == null) {
191            inactivationBlockingDefinitions = new HashSet<InactivationBlockingMetadata>();
192            inactivationBlockersForClass.put(inactivationBlockingDefinition.getBlockedBusinessObjectClass(), inactivationBlockingDefinitions);
193        }
194        boolean duplicateAdd = ! inactivationBlockingDefinitions.add(inactivationBlockingDefinition);
195        if (duplicateAdd) {
196            throw new DataDictionaryException("Detected duplicate InactivationBlockingDefinition for class " + inactivationBlockingDefinition.getBlockingReferenceBusinessObjectClass().getClass().getName());
197        }
198    }
199    
200    public void run() {
201        LOG.info( "Starting DD Index Building" );
202        buildDDIndicies();
203        LOG.info( "Completed DD Index Building" );
204//        LOG.info( "Starting DD Validation" );
205//        validateDD();
206//        LOG.info( "Ending DD Validation" );
207        LOG.info( "Started DD Inactivation Blocking Index Building" );
208        buildDDInactivationBlockingIndices();
209        LOG.info( "Completed DD Inactivation Blocking Index Building" );
210    }
211}