001    /**
002     * Copyright 2005-2013 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     */
016    package org.kuali.rice.kns.util;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.kuali.rice.core.api.CoreApiServiceLocator;
020    import org.kuali.rice.core.api.config.property.ConfigurationService;
021    import org.kuali.rice.kns.datadictionary.MaintainableCollectionDefinition;
022    import org.kuali.rice.kns.datadictionary.MaintainableFieldDefinition;
023    import org.kuali.rice.kns.datadictionary.MaintainableItemDefinition;
024    import org.kuali.rice.kns.datadictionary.MaintainableSectionDefinition;
025    import org.kuali.rice.kns.lookup.LookupUtils;
026    import org.kuali.rice.kns.maintenance.Maintainable;
027    import org.kuali.rice.kns.service.KNSServiceLocator;
028    import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
029    import org.kuali.rice.kns.web.ui.Field;
030    import org.kuali.rice.kns.web.ui.Row;
031    import org.kuali.rice.kns.web.ui.Section;
032    import org.kuali.rice.krad.bo.BusinessObject;
033    import org.kuali.rice.krad.datadictionary.AttributeSecurity;
034    import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
035    import org.kuali.rice.krad.exception.ValidationException;
036    import org.kuali.rice.krad.lookup.SelectiveReferenceRefresher;
037    import org.kuali.rice.krad.service.DataDictionaryService;
038    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
039    import org.kuali.rice.krad.service.KualiExceptionIncidentService;
040    import org.kuali.rice.krad.service.MaintenanceDocumentService;
041    import org.kuali.rice.krad.util.KRADConstants;
042    import org.kuali.rice.krad.workflow.service.WorkflowDocumentService;
043    
044    import java.util.Collection;
045    import java.util.HashMap;
046    import java.util.HashSet;
047    import java.util.Iterator;
048    import java.util.List;
049    import java.util.Map;
050    import java.util.Set;
051    import java.util.StringTokenizer;
052    
053    public final class MaintenanceUtils {
054        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintenanceUtils.class);
055    
056        private static MaintenanceDocumentService maintenanceDocumentService;
057        private static WorkflowDocumentService workflowDocumentService;
058        private static ConfigurationService kualiConfigurationService;
059        private static KualiExceptionIncidentService kualiExceptionIncidentService;
060        private static MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
061        private static DataDictionaryService dataDictionaryService;
062    
063        private MaintenanceUtils() {
064            throw new UnsupportedOperationException("do not call");
065        }
066    
067        /**
068         * Returns the field templates defined in the maint dictionary xml files. Field templates are used in multiple value lookups.
069         * When doing a MV lookup on a collection, the returned BOs are not necessarily of the same type as the elements of the
070         * collection. Therefore, a means of mapping between the fields for the 2 BOs are necessary. The template attribute of
071         * <maintainableField>s contained within <maintainableCollection>s tells us this mapping. Example: a
072         * <maintainableField name="collectionAttrib" template="lookupBOAttrib"> definition means that when a list of BOs are
073         * returned, the lookupBOAttrib value of the looked up BO will be placed into the collectionAttrib value of the BO added to the
074         * collection
075         *
076         * @param sections       the sections of a document
077         * @param collectionName the name of a collection. May be a nested collection with indices (e.g. collA[1].collB)
078         * @return
079         */
080        public static Map<String, String> generateMultipleValueLookupBOTemplate(List<MaintainableSectionDefinition> sections, String collectionName) {
081            MaintainableCollectionDefinition definition = findMaintainableCollectionDefinition(sections, collectionName);
082            if (definition == null) {
083                return null;
084            }
085            Map<String, String> template = null;
086    
087            for (MaintainableFieldDefinition maintainableField : definition.getMaintainableFields()) {
088                String templateString = maintainableField.getTemplate();
089                if (StringUtils.isNotBlank(templateString)) {
090                    if (template == null) {
091                        template = new HashMap<String, String>();
092                    }
093                    template.put(maintainableField.getName(), templateString);
094                }
095            }
096            return template;
097        }
098    
099        /**
100         * Finds the MaintainableCollectionDefinition corresponding to the given collection name. For example, if the collection name is
101         * "A.B.C", it will attempt to find the MaintainableCollectionDefinition for C that is nested in B that is nested under A. This
102         * may not work correctly if there are duplicate collection definitions within the sections
103         *
104         * @param sections       the sections of a maint doc
105         * @param collectionName the name of a collection, relative to the root of the BO being maintained. This value may have index
106         *                       values (e.g. [1]), but these are ignored.
107         * @return
108         */
109        public static MaintainableCollectionDefinition findMaintainableCollectionDefinition(List<MaintainableSectionDefinition> sections, String collectionName) {
110            String[] collectionNameParts = StringUtils.split(collectionName, ".");
111            for (MaintainableSectionDefinition section : sections) {
112                MaintainableCollectionDefinition collDefinition = findMaintainableCollectionDefinitionHelper(section.getMaintainableItems(), collectionNameParts, 0);
113                if (collDefinition != null) {
114                    return collDefinition;
115                }
116            }
117            return null;
118        }
119    
120        private static <E extends MaintainableItemDefinition> MaintainableCollectionDefinition findMaintainableCollectionDefinitionHelper(Collection<E> items, String[] collectionNameParts, int collectionNameIndex) {
121            if (collectionNameParts.length <= collectionNameIndex) {
122                // we've gone too far down the nesting without finding it
123                return null;
124            }
125    
126            // we only care about the coll name, and not the index, since the coll definitions do not include the indexing characters,
127            // i.e. [ and ]
128            String collectionToFind = StringUtils.substringBefore(collectionNameParts[collectionNameIndex], "[");
129            for (MaintainableItemDefinition item : items) {
130                if (item instanceof MaintainableCollectionDefinition) {
131                    MaintainableCollectionDefinition collection = (MaintainableCollectionDefinition) item;
132                    if (collection.getName().equals(collectionToFind)) {
133                        // we found an appropriate coll, now we have to see if we need to recurse even more (more nested collections),
134                        // or just return the one we found.
135                        if (collectionNameIndex == collectionNameParts.length - 1) {
136                            // we're at the last part of the name, so we return
137                            return collection;
138                        } else {
139                            // go deeper
140                            return findMaintainableCollectionDefinitionHelper(collection.getMaintainableCollections(), collectionNameParts, collectionNameIndex + 1);
141                        }
142                    }
143                }
144            }
145            return null;
146        }
147    
148        /**
149         * Checks to see if there has been an override lookup declared for the maintenance field. If so, the override will be used for
150         * the quickfinder and lookup utils will not be called. If no override was given, LookupUtils.setFieldQuickfinder will be called
151         * to set the system generated quickfinder based on the attributes relationship to the parent business object.
152         *
153         * @return Field with quickfinder set if one was found
154         */
155        public static final Field setFieldQuickfinder(BusinessObject businessObject, String attributeName, MaintainableFieldDefinition maintainableFieldDefinition, Field field, List<String> displayedFieldNames, SelectiveReferenceRefresher srr) {
156            if (maintainableFieldDefinition.getOverrideLookupClass() != null && StringUtils.isNotBlank(maintainableFieldDefinition.getOverrideFieldConversions())) {
157                field.setQuickFinderClassNameImpl(maintainableFieldDefinition.getOverrideLookupClass().getName());
158                field.setFieldConversions(maintainableFieldDefinition.getOverrideFieldConversions());
159                field.setBaseLookupUrl(LookupUtils.getBaseLookupUrl(false));
160                field.setReferencesToRefresh(LookupUtils.convertReferencesToSelectCollectionToString(
161                        srr.getAffectedReferencesFromLookup(businessObject, attributeName, "")));
162                return field;
163            }
164            if (maintainableFieldDefinition.isNoLookup()) {
165                return field;
166            }
167            return LookupUtils.setFieldQuickfinder(businessObject, null, false, 0, attributeName, field, displayedFieldNames, maintainableFieldDefinition.isNoLookup());
168        }
169    
170        public static final Field setFieldQuickfinder(BusinessObject businessObject, String collectionName, boolean addLine, int index,
171                                                      String attributeName, Field field, List<String> displayedFieldNames, Maintainable maintainable, MaintainableFieldDefinition maintainableFieldDefinition) {
172            if (maintainableFieldDefinition.getOverrideLookupClass() != null && StringUtils.isNotBlank(maintainableFieldDefinition.getOverrideFieldConversions())) {
173                if (maintainable != null) {
174                    String collectionPrefix = "";
175                    if (collectionName != null) {
176                        if (addLine) {
177                            collectionPrefix = KRADConstants.MAINTENANCE_ADD_PREFIX + collectionName + ".";
178                        } else {
179                            collectionPrefix = collectionName + "[" + index + "].";
180                        }
181                    }
182                    field.setQuickFinderClassNameImpl(maintainableFieldDefinition.getOverrideLookupClass().getName());
183    
184                    String prefixedFieldConversions = prefixFieldConversionsDestinationsWithCollectionPrefix(maintainableFieldDefinition.getOverrideFieldConversions(), collectionPrefix);
185                    field.setFieldConversions(prefixedFieldConversions);
186                    field.setBaseLookupUrl(LookupUtils.getBaseLookupUrl(false));
187                    field.setReferencesToRefresh(LookupUtils.convertReferencesToSelectCollectionToString(
188                            maintainable.getAffectedReferencesFromLookup(businessObject, attributeName, collectionPrefix)));
189                }
190                return field;
191            }
192            if (maintainableFieldDefinition.isNoLookup()) {
193                return field;
194            }
195            return LookupUtils.setFieldQuickfinder(businessObject, collectionName, addLine, index,
196                    attributeName, field, displayedFieldNames, maintainable);
197        }
198    
199        private static String prefixFieldConversionsDestinationsWithCollectionPrefix(String originalFieldConversions, String collectionPrefix) {
200            StringBuilder buf = new StringBuilder();
201            StringTokenizer tok = new StringTokenizer(originalFieldConversions, KRADConstants.FIELD_CONVERSIONS_SEPARATOR);
202            boolean needsSeparator = false;
203            while (tok.hasMoreTokens()) {
204                String conversionPair = tok.nextToken();
205                if (StringUtils.isBlank(conversionPair)) {
206                    continue;
207                }
208    
209                String fromValue = StringUtils.substringBefore(conversionPair, KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR);
210                String toValue = StringUtils.substringAfter(conversionPair, KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR);
211    
212                if (needsSeparator) {
213                    buf.append(KRADConstants.FIELD_CONVERSIONS_SEPARATOR);
214                }
215                needsSeparator = true;
216    
217                buf.append(fromValue).append(KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR).append(collectionPrefix).append(toValue);
218            }
219            return buf.toString();
220        }
221    
222        public static final void setFieldDirectInquiry(BusinessObject businessObject, String attributeName, MaintainableFieldDefinition maintainableFieldDefinition, Field field, List<String> displayedFieldNames) {
223            LookupUtils.setFieldDirectInquiry(businessObject, attributeName, field);
224        }
225    
226        public static final void setFieldDirectInquiry(BusinessObject businessObject, String collectionName, boolean addLine, int index,
227                                                       String attributeName, Field field, List<String> displayedFieldNames, Maintainable maintainable, MaintainableFieldDefinition maintainableFieldDefinition) {
228            LookupUtils.setFieldDirectInquiry(businessObject, attributeName, field);
229        }
230    
231        /**
232         * Given a section, returns a comma delimited string of all fields, representing the error keys that exist for a section
233         *
234         * @param section a section
235         * @return
236         */
237        public static String generateErrorKeyForSection(Section section) {
238            Set<String> fieldPropertyNames = new HashSet<String>();
239            addRowsToErrorKeySet(section.getRows(), fieldPropertyNames);
240    
241            StringBuilder buf = new StringBuilder();
242            buf.append(section.getSectionId()).append(",");
243    
244            Iterator<String> nameIter = fieldPropertyNames.iterator();
245            while (nameIter.hasNext()) {
246                buf.append(nameIter.next());
247                if (nameIter.hasNext()) {
248                    buf.append(",");
249                }
250            }
251    
252            if (section.getContainedCollectionNames() != null && section.getContainedCollectionNames().size() > 0) {
253                buf.append(",");
254    
255                Iterator<String> collectionIter = section.getContainedCollectionNames().iterator();
256                while (collectionIter.hasNext()) {
257                    buf.append(KRADConstants.MAINTENANCE_NEW_MAINTAINABLE + collectionIter.next());
258                    if (collectionIter.hasNext()) {
259                        buf.append(",");
260                    }
261                }
262            }
263    
264            return buf.toString();
265        }
266    
267        /**
268         * This method recurses through all the fields of the list of rows and adds each field's property name to the set if it starts
269         * with Constants.MAINTENANCE_NEW_MAINTAINABLE
270         *
271         * @param listOfRows
272         * @param errorKeys
273         * @see KRADConstants#MAINTENANCE_NEW_MAINTAINABLE
274         */
275        protected static void addRowsToErrorKeySet(List<Row> listOfRows, Set<String> errorKeys) {
276            if (listOfRows == null) {
277                return;
278            }
279            for (Row row : listOfRows) {
280                List<Field> fields = row.getFields();
281                if (fields == null) {
282                    continue;
283                }
284                for (Field field : fields) {
285                    String fieldPropertyName = field.getPropertyName();
286                    if (fieldPropertyName != null && fieldPropertyName.startsWith(KRADConstants.MAINTENANCE_NEW_MAINTAINABLE)) {
287                        errorKeys.add(field.getPropertyName());
288                    }
289                    addRowsToErrorKeySet(field.getContainerRows(), errorKeys);
290                }
291            }
292        }
293    
294        /**
295         * This method will throw a {@link ValidationException} if there is a valid locking document in existence and throwExceptionIfLocked is true.
296         */
297        public static void checkForLockingDocument(Maintainable maintainable, boolean throwExceptionIfLocked) {
298            LOG.info("starting checkForLockingDocument (by Maintainable)");
299    
300            // get the docHeaderId of the blocking docs, if any are locked and blocking
301            //String blockingDocId = getMaintenanceDocumentService().getLockingDocumentId(maintainable, null);
302            String blockingDocId = maintainable.getLockingDocumentId();
303            org.kuali.rice.krad.maintenance.MaintenanceUtils
304                    .checkDocumentBlockingDocumentId(blockingDocId, throwExceptionIfLocked);
305        }
306    
307        private static MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() {
308            if (maintenanceDocumentDictionaryService == null) {
309                maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService();
310            }
311            return maintenanceDocumentDictionaryService;
312        }
313    
314        private static DataDictionaryService getDataDictionaryService() {
315            if (dataDictionaryService == null) {
316                dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
317            }
318            return dataDictionaryService;
319        }
320    
321        public static Map<String, AttributeSecurity> retrievePropertyPathToAttributeSecurityMappings(String docTypeName) {
322            Map<String, AttributeSecurity> results = new HashMap<String, AttributeSecurity>();
323            MaintenanceDocumentEntry entry = getMaintenanceDocumentDictionaryService().getMaintenanceDocumentEntry(docTypeName);
324            String className = entry.getDataObjectClass().getName();
325    
326            for (MaintainableSectionDefinition section : entry.getMaintainableSections()) {
327                for (MaintainableItemDefinition item : section.getMaintainableItems()) {
328                    if (item instanceof MaintainableFieldDefinition) {
329                        MaintainableFieldDefinition field = (MaintainableFieldDefinition) item;
330                        AttributeSecurity attributeSecurity = getDataDictionaryService().getAttributeSecurity(className, field.getName());
331                        if (attributeSecurity != null) {
332                            results.put(field.getName(), attributeSecurity);
333                        }
334                    } else if (item instanceof MaintainableCollectionDefinition) {
335                        addMaintenanceDocumentCollectionPathToSecurityMappings(results, "", (MaintainableCollectionDefinition) item);
336                    }
337                }
338            }
339            return results;
340        }
341    
342        private static void addMaintenanceDocumentCollectionPathToSecurityMappings(Map<String, AttributeSecurity> mappings, String propertyPathPrefix, MaintainableCollectionDefinition collectionDefinition) {
343            propertyPathPrefix = propertyPathPrefix + collectionDefinition.getName() + ".";
344            String boClassName = collectionDefinition.getBusinessObjectClass().getName();
345            for (MaintainableFieldDefinition field : collectionDefinition.getMaintainableFields()) {
346                AttributeSecurity attributeSecurity = getDataDictionaryService().getAttributeSecurity(boClassName, field.getName());
347                if (attributeSecurity != null) {
348                    mappings.put(propertyPathPrefix + field.getName(), attributeSecurity);
349                }
350            }
351            for (MaintainableCollectionDefinition nestedCollection : collectionDefinition.getMaintainableCollections()) {
352                addMaintenanceDocumentCollectionPathToSecurityMappings(mappings, propertyPathPrefix, nestedCollection);
353            }
354        }
355    
356        private static MaintenanceDocumentService getMaintenanceDocumentService() {
357            if (maintenanceDocumentService == null) {
358                maintenanceDocumentService = KRADServiceLocatorWeb.getMaintenanceDocumentService();
359            }
360            return maintenanceDocumentService;
361        }
362    
363        private static WorkflowDocumentService getWorkflowDocumentService() {
364            if (workflowDocumentService == null) {
365                workflowDocumentService = KRADServiceLocatorWeb.getWorkflowDocumentService();
366            }
367            return workflowDocumentService;
368        }
369    
370        private static ConfigurationService getKualiConfigurationService() {
371            if (kualiConfigurationService == null) {
372                kualiConfigurationService = CoreApiServiceLocator.getKualiConfigurationService();
373            }
374            return kualiConfigurationService;
375        }
376    
377        private static KualiExceptionIncidentService getKualiExceptionIncidentService() {
378            if (kualiExceptionIncidentService == null) {
379                kualiExceptionIncidentService = KRADServiceLocatorWeb.getKualiExceptionIncidentService();
380            }
381            return kualiExceptionIncidentService;
382        }
383    }