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.kns.util;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.CoreApiServiceLocator;
020import org.kuali.rice.core.api.config.property.ConfigurationService;
021import org.kuali.rice.kns.datadictionary.MaintainableCollectionDefinition;
022import org.kuali.rice.kns.datadictionary.MaintainableFieldDefinition;
023import org.kuali.rice.kns.datadictionary.MaintainableItemDefinition;
024import org.kuali.rice.kns.datadictionary.MaintainableSectionDefinition;
025import org.kuali.rice.kns.lookup.LookupUtils;
026import org.kuali.rice.kns.maintenance.Maintainable;
027import org.kuali.rice.kns.service.KNSServiceLocator;
028import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
029import org.kuali.rice.kns.web.ui.Field;
030import org.kuali.rice.kns.web.ui.Row;
031import org.kuali.rice.kns.web.ui.Section;
032import org.kuali.rice.krad.bo.BusinessObject;
033import org.kuali.rice.krad.datadictionary.AttributeSecurity;
034import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
035import org.kuali.rice.krad.exception.ValidationException;
036import org.kuali.rice.krad.lookup.SelectiveReferenceRefresher;
037import org.kuali.rice.krad.service.DataDictionaryService;
038import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
039import org.kuali.rice.krad.service.KualiExceptionIncidentService;
040import org.kuali.rice.krad.service.MaintenanceDocumentService;
041import org.kuali.rice.krad.util.KRADConstants;
042import org.kuali.rice.krad.workflow.service.WorkflowDocumentService;
043
044import java.util.Collection;
045import java.util.HashMap;
046import java.util.HashSet;
047import java.util.Iterator;
048import java.util.List;
049import java.util.Map;
050import java.util.Set;
051import java.util.StringTokenizer;
052
053public 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}