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}