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 }