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}