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 }