View Javadoc

1   /*
2    * Copyright 2006-2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.kuali.rice.kns.util;
18  
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.config.property.ConfigurationService;
21  import org.kuali.rice.kns.datadictionary.MaintainableCollectionDefinition;
22  import org.kuali.rice.kns.datadictionary.MaintainableFieldDefinition;
23  import org.kuali.rice.kns.datadictionary.MaintainableItemDefinition;
24  import org.kuali.rice.kns.datadictionary.MaintainableSectionDefinition;
25  import org.kuali.rice.kns.lookup.LookupUtils;
26  import org.kuali.rice.kns.maintenance.Maintainable;
27  import org.kuali.rice.kns.service.KNSServiceLocator;
28  import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
29  import org.kuali.rice.kns.web.ui.Field;
30  import org.kuali.rice.kns.web.ui.Row;
31  import org.kuali.rice.kns.web.ui.Section;
32  import org.kuali.rice.krad.bo.BusinessObject;
33  import org.kuali.rice.krad.datadictionary.AttributeSecurity;
34  import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
35  import org.kuali.rice.krad.exception.ValidationException;
36  import org.kuali.rice.krad.lookup.SelectiveReferenceRefresher;
37  import org.kuali.rice.krad.service.DataDictionaryService;
38  import org.kuali.rice.krad.service.KRADServiceLocator;
39  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
40  import org.kuali.rice.krad.service.KualiExceptionIncidentService;
41  import org.kuali.rice.krad.service.MaintenanceDocumentService;
42  import org.kuali.rice.krad.util.KRADConstants;
43  import org.kuali.rice.krad.workflow.service.WorkflowDocumentService;
44  
45  import java.util.Collection;
46  import java.util.HashMap;
47  import java.util.HashSet;
48  import java.util.Iterator;
49  import java.util.List;
50  import java.util.Map;
51  import java.util.Set;
52  import java.util.StringTokenizer;
53  
54  public final class MaintenanceUtils {
55      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintenanceUtils.class);
56  
57      private static MaintenanceDocumentService maintenanceDocumentService;
58      private static WorkflowDocumentService workflowDocumentService;
59      private static ConfigurationService kualiConfigurationService;
60      private static KualiExceptionIncidentService kualiExceptionIncidentService;
61      private static MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
62      private static DataDictionaryService dataDictionaryService;
63  
64      private MaintenanceUtils() {
65          throw new UnsupportedOperationException("do not call");
66      }
67  
68      /**
69       * Returns the field templates defined in the maint dictionary xml files. Field templates are used in multiple value lookups.
70       * When doing a MV lookup on a collection, the returned BOs are not necessarily of the same type as the elements of the
71       * collection. Therefore, a means of mapping between the fields for the 2 BOs are necessary. The template attribute of
72       * <maintainableField>s contained within <maintainableCollection>s tells us this mapping. Example: a
73       * <maintainableField name="collectionAttrib" template="lookupBOAttrib"> definition means that when a list of BOs are
74       * returned, the lookupBOAttrib value of the looked up BO will be placed into the collectionAttrib value of the BO added to the
75       * collection
76       *
77       * @param sections       the sections of a document
78       * @param collectionName the name of a collection. May be a nested collection with indices (e.g. collA[1].collB)
79       * @return
80       */
81      public static Map<String, String> generateMultipleValueLookupBOTemplate(List<MaintainableSectionDefinition> sections, String collectionName) {
82          MaintainableCollectionDefinition definition = findMaintainableCollectionDefinition(sections, collectionName);
83          if (definition == null) {
84              return null;
85          }
86          Map<String, String> template = null;
87  
88          for (MaintainableFieldDefinition maintainableField : definition.getMaintainableFields()) {
89              String templateString = maintainableField.getTemplate();
90              if (StringUtils.isNotBlank(templateString)) {
91                  if (template == null) {
92                      template = new HashMap<String, String>();
93                  }
94                  template.put(maintainableField.getName(), templateString);
95              }
96          }
97          return template;
98      }
99  
100     /**
101      * Finds the MaintainableCollectionDefinition corresponding to the given collection name. For example, if the collection name is
102      * "A.B.C", it will attempt to find the MaintainableCollectionDefinition for C that is nested in B that is nested under A. This
103      * may not work correctly if there are duplicate collection definitions within the sections
104      *
105      * @param sections       the sections of a maint doc
106      * @param collectionName the name of a collection, relative to the root of the BO being maintained. This value may have index
107      *                       values (e.g. [1]), but these are ignored.
108      * @return
109      */
110     public static MaintainableCollectionDefinition findMaintainableCollectionDefinition(List<MaintainableSectionDefinition> sections, String collectionName) {
111         String[] collectionNameParts = StringUtils.split(collectionName, ".");
112         for (MaintainableSectionDefinition section : sections) {
113             MaintainableCollectionDefinition collDefinition = findMaintainableCollectionDefinitionHelper(section.getMaintainableItems(), collectionNameParts, 0);
114             if (collDefinition != null) {
115                 return collDefinition;
116             }
117         }
118         return null;
119     }
120 
121     private static <E extends MaintainableItemDefinition> MaintainableCollectionDefinition findMaintainableCollectionDefinitionHelper(Collection<E> items, String[] collectionNameParts, int collectionNameIndex) {
122         if (collectionNameParts.length <= collectionNameIndex) {
123             // we've gone too far down the nesting without finding it
124             return null;
125         }
126 
127         // we only care about the coll name, and not the index, since the coll definitions do not include the indexing characters,
128         // i.e. [ and ]
129         String collectionToFind = StringUtils.substringBefore(collectionNameParts[collectionNameIndex], "[");
130         for (MaintainableItemDefinition item : items) {
131             if (item instanceof MaintainableCollectionDefinition) {
132                 MaintainableCollectionDefinition collection = (MaintainableCollectionDefinition) item;
133                 if (collection.getName().equals(collectionToFind)) {
134                     // we found an appropriate coll, now we have to see if we need to recurse even more (more nested collections),
135                     // or just return the one we found.
136                     if (collectionNameIndex == collectionNameParts.length - 1) {
137                         // we're at the last part of the name, so we return
138                         return collection;
139                     } else {
140                         // go deeper
141                         return findMaintainableCollectionDefinitionHelper(collection.getMaintainableCollections(), collectionNameParts, collectionNameIndex + 1);
142                     }
143                 }
144             }
145         }
146         return null;
147     }
148 
149     /**
150      * Checks to see if there has been an override lookup declared for the maintenance field. If so, the override will be used for
151      * the quickfinder and lookup utils will not be called. If no override was given, LookupUtils.setFieldQuickfinder will be called
152      * to set the system generated quickfinder based on the attributes relationship to the parent business object.
153      *
154      * @return Field with quickfinder set if one was found
155      */
156     public static final Field setFieldQuickfinder(BusinessObject businessObject, String attributeName, MaintainableFieldDefinition maintainableFieldDefinition, Field field, List<String> displayedFieldNames, SelectiveReferenceRefresher srr) {
157         if (maintainableFieldDefinition.getOverrideLookupClass() != null && StringUtils.isNotBlank(maintainableFieldDefinition.getOverrideFieldConversions())) {
158             field.setQuickFinderClassNameImpl(maintainableFieldDefinition.getOverrideLookupClass().getName());
159             field.setFieldConversions(maintainableFieldDefinition.getOverrideFieldConversions());
160             field.setBaseLookupUrl(LookupUtils.getBaseLookupUrl(false));
161             field.setReferencesToRefresh(LookupUtils.convertReferencesToSelectCollectionToString(
162                     srr.getAffectedReferencesFromLookup(businessObject, attributeName, "")));
163             return field;
164         }
165         if (maintainableFieldDefinition.isNoLookup()) {
166             return field;
167         }
168         return LookupUtils.setFieldQuickfinder(businessObject, null, false, 0, attributeName, field, displayedFieldNames, maintainableFieldDefinition.isNoLookup());
169     }
170 
171     public static final Field setFieldQuickfinder(BusinessObject businessObject, String collectionName, boolean addLine, int index,
172                                                   String attributeName, Field field, List<String> displayedFieldNames, Maintainable maintainable, MaintainableFieldDefinition maintainableFieldDefinition) {
173         if (maintainableFieldDefinition.getOverrideLookupClass() != null && StringUtils.isNotBlank(maintainableFieldDefinition.getOverrideFieldConversions())) {
174             if (maintainable != null) {
175                 String collectionPrefix = "";
176                 if (collectionName != null) {
177                     if (addLine) {
178                         collectionPrefix = KRADConstants.MAINTENANCE_ADD_PREFIX + collectionName + ".";
179                     } else {
180                         collectionPrefix = collectionName + "[" + index + "].";
181                     }
182                 }
183                 field.setQuickFinderClassNameImpl(maintainableFieldDefinition.getOverrideLookupClass().getName());
184 
185                 String prefixedFieldConversions = prefixFieldConversionsDestinationsWithCollectionPrefix(maintainableFieldDefinition.getOverrideFieldConversions(), collectionPrefix);
186                 field.setFieldConversions(prefixedFieldConversions);
187                 field.setBaseLookupUrl(LookupUtils.getBaseLookupUrl(false));
188                 field.setReferencesToRefresh(LookupUtils.convertReferencesToSelectCollectionToString(
189                         maintainable.getAffectedReferencesFromLookup(businessObject, attributeName, collectionPrefix)));
190             }
191             return field;
192         }
193         if (maintainableFieldDefinition.isNoLookup()) {
194             return field;
195         }
196         return LookupUtils.setFieldQuickfinder(businessObject, collectionName, addLine, index,
197                 attributeName, field, displayedFieldNames, maintainable);
198     }
199 
200     private static String prefixFieldConversionsDestinationsWithCollectionPrefix(String originalFieldConversions, String collectionPrefix) {
201         StringBuilder buf = new StringBuilder();
202         StringTokenizer tok = new StringTokenizer(originalFieldConversions, KRADConstants.FIELD_CONVERSIONS_SEPARATOR);
203         boolean needsSeparator = false;
204         while (tok.hasMoreTokens()) {
205             String conversionPair = tok.nextToken();
206             if (StringUtils.isBlank(conversionPair)) {
207                 continue;
208             }
209 
210             String fromValue = StringUtils.substringBefore(conversionPair, KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR);
211             String toValue = StringUtils.substringAfter(conversionPair, KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR);
212 
213             if (needsSeparator) {
214                 buf.append(KRADConstants.FIELD_CONVERSIONS_SEPARATOR);
215             }
216             needsSeparator = true;
217 
218             buf.append(fromValue).append(KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR).append(collectionPrefix).append(toValue);
219         }
220         return buf.toString();
221     }
222 
223     public static final void setFieldDirectInquiry(BusinessObject businessObject, String attributeName, MaintainableFieldDefinition maintainableFieldDefinition, Field field, List<String> displayedFieldNames) {
224         LookupUtils.setFieldDirectInquiry(businessObject, attributeName, field);
225     }
226 
227     public static final void setFieldDirectInquiry(BusinessObject businessObject, String collectionName, boolean addLine, int index,
228                                                    String attributeName, Field field, List<String> displayedFieldNames, Maintainable maintainable, MaintainableFieldDefinition maintainableFieldDefinition) {
229         LookupUtils.setFieldDirectInquiry(businessObject, attributeName, field);
230     }
231 
232     /**
233      * Given a section, returns a comma delimited string of all fields, representing the error keys that exist for a section
234      *
235      * @param section a section
236      * @return
237      */
238     public static String generateErrorKeyForSection(Section section) {
239         Set<String> fieldPropertyNames = new HashSet<String>();
240         addRowsToErrorKeySet(section.getRows(), fieldPropertyNames);
241 
242         StringBuilder buf = new StringBuilder();
243         buf.append(section.getSectionId()).append(",");
244 
245         Iterator<String> nameIter = fieldPropertyNames.iterator();
246         while (nameIter.hasNext()) {
247             buf.append(nameIter.next());
248             if (nameIter.hasNext()) {
249                 buf.append(",");
250             }
251         }
252 
253         if (section.getContainedCollectionNames() != null && section.getContainedCollectionNames().size() > 0) {
254             buf.append(",");
255 
256             Iterator<String> collectionIter = section.getContainedCollectionNames().iterator();
257             while (collectionIter.hasNext()) {
258                 buf.append(KRADConstants.MAINTENANCE_NEW_MAINTAINABLE + collectionIter.next());
259                 if (collectionIter.hasNext()) {
260                     buf.append(",");
261                 }
262             }
263         }
264 
265         return buf.toString();
266     }
267 
268     /**
269      * 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
270      * with Constants.MAINTENANCE_NEW_MAINTAINABLE
271      *
272      * @param listOfRows
273      * @param errorKeys
274      * @see KRADConstants#MAINTENANCE_NEW_MAINTAINABLE
275      */
276     protected static void addRowsToErrorKeySet(List<Row> listOfRows, Set<String> errorKeys) {
277         if (listOfRows == null) {
278             return;
279         }
280         for (Row row : listOfRows) {
281             List<Field> fields = row.getFields();
282             if (fields == null) {
283                 continue;
284             }
285             for (Field field : fields) {
286                 String fieldPropertyName = field.getPropertyName();
287                 if (fieldPropertyName != null && fieldPropertyName.startsWith(KRADConstants.MAINTENANCE_NEW_MAINTAINABLE)) {
288                     errorKeys.add(field.getPropertyName());
289                 }
290                 addRowsToErrorKeySet(field.getContainerRows(), errorKeys);
291             }
292         }
293     }
294 
295     /**
296      * This method will throw a {@link ValidationException} if there is a valid locking document in existence and throwExceptionIfLocked is true.
297      */
298     public static void checkForLockingDocument(Maintainable maintainable, boolean throwExceptionIfLocked) {
299         LOG.info("starting checkForLockingDocument (by Maintainable)");
300 
301         // get the docHeaderId of the blocking docs, if any are locked and blocking
302         //String blockingDocId = getMaintenanceDocumentService().getLockingDocumentId(maintainable, null);
303         String blockingDocId = maintainable.getLockingDocumentId();
304         org.kuali.rice.krad.maintenance.MaintenanceUtils
305                 .checkDocumentBlockingDocumentId(blockingDocId, throwExceptionIfLocked);
306     }
307 
308     private static MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() {
309         if (maintenanceDocumentDictionaryService == null) {
310             maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService();
311         }
312         return maintenanceDocumentDictionaryService;
313     }
314 
315     private static DataDictionaryService getDataDictionaryService() {
316         if (dataDictionaryService == null) {
317             dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
318         }
319         return dataDictionaryService;
320     }
321 
322     public static Map<String, AttributeSecurity> retrievePropertyPathToAttributeSecurityMappings(String docTypeName) {
323         Map<String, AttributeSecurity> results = new HashMap<String, AttributeSecurity>();
324         MaintenanceDocumentEntry entry = getMaintenanceDocumentDictionaryService().getMaintenanceDocumentEntry(docTypeName);
325         String className = entry.getDataObjectClass().getName();
326 
327         for (MaintainableSectionDefinition section : entry.getMaintainableSections()) {
328             for (MaintainableItemDefinition item : section.getMaintainableItems()) {
329                 if (item instanceof MaintainableFieldDefinition) {
330                     MaintainableFieldDefinition field = (MaintainableFieldDefinition) item;
331                     AttributeSecurity attributeSecurity = getDataDictionaryService().getAttributeSecurity(className, field.getName());
332                     if (attributeSecurity != null) {
333                         results.put(field.getName(), attributeSecurity);
334                     }
335                 } else if (item instanceof MaintainableCollectionDefinition) {
336                     addMaintenanceDocumentCollectionPathToSecurityMappings(results, "", (MaintainableCollectionDefinition) item);
337                 }
338             }
339         }
340         return results;
341     }
342 
343     private static void addMaintenanceDocumentCollectionPathToSecurityMappings(Map<String, AttributeSecurity> mappings, String propertyPathPrefix, MaintainableCollectionDefinition collectionDefinition) {
344         propertyPathPrefix = propertyPathPrefix + collectionDefinition.getName() + ".";
345         String boClassName = collectionDefinition.getBusinessObjectClass().getName();
346         for (MaintainableFieldDefinition field : collectionDefinition.getMaintainableFields()) {
347             AttributeSecurity attributeSecurity = getDataDictionaryService().getAttributeSecurity(boClassName, field.getName());
348             if (attributeSecurity != null) {
349                 mappings.put(propertyPathPrefix + field.getName(), attributeSecurity);
350             }
351         }
352         for (MaintainableCollectionDefinition nestedCollection : collectionDefinition.getMaintainableCollections()) {
353             addMaintenanceDocumentCollectionPathToSecurityMappings(mappings, propertyPathPrefix, nestedCollection);
354         }
355     }
356 
357     private static MaintenanceDocumentService getMaintenanceDocumentService() {
358         if (maintenanceDocumentService == null) {
359             maintenanceDocumentService = KRADServiceLocatorWeb.getMaintenanceDocumentService();
360         }
361         return maintenanceDocumentService;
362     }
363 
364     private static WorkflowDocumentService getWorkflowDocumentService() {
365         if (workflowDocumentService == null) {
366             workflowDocumentService = KRADServiceLocatorWeb.getWorkflowDocumentService();
367         }
368         return workflowDocumentService;
369     }
370 
371     private static ConfigurationService getKualiConfigurationService() {
372         if (kualiConfigurationService == null) {
373             kualiConfigurationService = KRADServiceLocator.getKualiConfigurationService();
374         }
375         return kualiConfigurationService;
376     }
377 
378     private static KualiExceptionIncidentService getKualiExceptionIncidentService() {
379         if (kualiExceptionIncidentService == null) {
380             kualiExceptionIncidentService = KRADServiceLocatorWeb.getKualiExceptionIncidentService();
381         }
382         return kualiExceptionIncidentService;
383     }
384 }