View Javadoc

1   /**
2    * Copyright 2005-2012 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  package org.kuali.rice.kns.util;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.config.property.ConfigurationService;
20  import org.kuali.rice.kns.datadictionary.MaintainableCollectionDefinition;
21  import org.kuali.rice.kns.datadictionary.MaintainableFieldDefinition;
22  import org.kuali.rice.kns.datadictionary.MaintainableItemDefinition;
23  import org.kuali.rice.kns.datadictionary.MaintainableSectionDefinition;
24  import org.kuali.rice.kns.lookup.LookupUtils;
25  import org.kuali.rice.kns.maintenance.Maintainable;
26  import org.kuali.rice.kns.service.KNSServiceLocator;
27  import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
28  import org.kuali.rice.kns.web.ui.Field;
29  import org.kuali.rice.kns.web.ui.Row;
30  import org.kuali.rice.kns.web.ui.Section;
31  import org.kuali.rice.krad.bo.BusinessObject;
32  import org.kuali.rice.krad.datadictionary.AttributeSecurity;
33  import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
34  import org.kuali.rice.krad.exception.ValidationException;
35  import org.kuali.rice.krad.lookup.SelectiveReferenceRefresher;
36  import org.kuali.rice.krad.service.DataDictionaryService;
37  import org.kuali.rice.krad.service.KRADServiceLocator;
38  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
39  import org.kuali.rice.krad.service.KualiExceptionIncidentService;
40  import org.kuali.rice.krad.service.MaintenanceDocumentService;
41  import org.kuali.rice.krad.util.KRADConstants;
42  import org.kuali.rice.krad.workflow.service.WorkflowDocumentService;
43  
44  import java.util.Collection;
45  import java.util.HashMap;
46  import java.util.HashSet;
47  import java.util.Iterator;
48  import java.util.List;
49  import java.util.Map;
50  import java.util.Set;
51  import java.util.StringTokenizer;
52  
53  public final class MaintenanceUtils {
54      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintenanceUtils.class);
55  
56      private static MaintenanceDocumentService maintenanceDocumentService;
57      private static WorkflowDocumentService workflowDocumentService;
58      private static ConfigurationService kualiConfigurationService;
59      private static KualiExceptionIncidentService kualiExceptionIncidentService;
60      private static MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
61      private static DataDictionaryService dataDictionaryService;
62  
63      private MaintenanceUtils() {
64          throw new UnsupportedOperationException("do not call");
65      }
66  
67      /**
68       * Returns the field templates defined in the maint dictionary xml files. Field templates are used in multiple value lookups.
69       * When doing a MV lookup on a collection, the returned BOs are not necessarily of the same type as the elements of the
70       * collection. Therefore, a means of mapping between the fields for the 2 BOs are necessary. The template attribute of
71       * <maintainableField>s contained within <maintainableCollection>s tells us this mapping. Example: a
72       * <maintainableField name="collectionAttrib" template="lookupBOAttrib"> definition means that when a list of BOs are
73       * returned, the lookupBOAttrib value of the looked up BO will be placed into the collectionAttrib value of the BO added to the
74       * collection
75       *
76       * @param sections       the sections of a document
77       * @param collectionName the name of a collection. May be a nested collection with indices (e.g. collA[1].collB)
78       * @return
79       */
80      public static Map<String, String> generateMultipleValueLookupBOTemplate(List<MaintainableSectionDefinition> sections, String collectionName) {
81          MaintainableCollectionDefinition definition = findMaintainableCollectionDefinition(sections, collectionName);
82          if (definition == null) {
83              return null;
84          }
85          Map<String, String> template = null;
86  
87          for (MaintainableFieldDefinition maintainableField : definition.getMaintainableFields()) {
88              String templateString = maintainableField.getTemplate();
89              if (StringUtils.isNotBlank(templateString)) {
90                  if (template == null) {
91                      template = new HashMap<String, String>();
92                  }
93                  template.put(maintainableField.getName(), templateString);
94              }
95          }
96          return template;
97      }
98  
99      /**
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 = KRADServiceLocator.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 }