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.krms.impl.ui; 017 018 import org.apache.commons.collections.CollectionUtils; 019 import org.apache.commons.lang.StringUtils; 020 import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException; 021 import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader; 022 import org.kuali.rice.core.api.uif.DataType; 023 import org.kuali.rice.core.api.uif.RemotableAttributeField; 024 import org.kuali.rice.core.api.uif.RemotableTextInput; 025 import org.kuali.rice.core.api.util.tree.Node; 026 import org.kuali.rice.core.api.util.tree.Tree; 027 import org.kuali.rice.core.impl.cache.DistributedCacheManagerDecorator; 028 import org.kuali.rice.krad.bo.PersistableBusinessObject; 029 import org.kuali.rice.krad.maintenance.MaintenanceDocument; 030 import org.kuali.rice.krad.maintenance.Maintainable; 031 import org.kuali.rice.krad.maintenance.MaintainableImpl; 032 import org.kuali.rice.krad.service.BusinessObjectService; 033 import org.kuali.rice.krad.service.KRADServiceLocator; 034 import org.kuali.rice.krad.service.SequenceAccessorService; 035 import org.kuali.rice.krad.uif.container.CollectionGroup; 036 import org.kuali.rice.krad.uif.container.Container; 037 import org.kuali.rice.krad.uif.view.View; 038 import org.kuali.rice.krad.util.KRADConstants; 039 import org.kuali.rice.krad.web.form.MaintenanceDocumentForm; 040 import org.kuali.rice.krms.api.KrmsConstants; 041 import org.kuali.rice.krms.api.repository.action.ActionDefinition; 042 import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition; 043 import org.kuali.rice.krms.api.repository.agenda.AgendaItemDefinition; 044 import org.kuali.rice.krms.api.repository.agenda.AgendaTreeDefinition; 045 import org.kuali.rice.krms.api.repository.context.ContextDefinition; 046 import org.kuali.rice.krms.api.repository.proposition.PropositionDefinition; 047 import org.kuali.rice.krms.api.repository.rule.RuleDefinition; 048 import org.kuali.rice.krms.api.repository.term.TermDefinition; 049 import org.kuali.rice.krms.api.repository.term.TermResolverDefinition; 050 import org.kuali.rice.krms.api.repository.term.TermSpecificationDefinition; 051 import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition; 052 import org.kuali.rice.krms.api.repository.type.KrmsTypeDefinition; 053 import org.kuali.rice.krms.impl.repository.ActionBo; 054 import org.kuali.rice.krms.impl.repository.AgendaBo; 055 import org.kuali.rice.krms.impl.repository.AgendaItemBo; 056 import org.kuali.rice.krms.impl.repository.ContextBoService; 057 import org.kuali.rice.krms.impl.repository.KrmsAttributeDefinitionService; 058 import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator; 059 import org.kuali.rice.krms.impl.repository.PropositionBo; 060 import org.kuali.rice.krms.impl.repository.PropositionParameterBo; 061 import org.kuali.rice.krms.impl.repository.RuleBo; 062 import org.kuali.rice.krms.impl.repository.TermBo; 063 import org.kuali.rice.krms.impl.repository.TermParameterBo; 064 import org.kuali.rice.krms.impl.util.KrmsImplConstants; 065 import org.kuali.rice.krms.impl.util.KrmsRetriever; 066 067 import java.util.ArrayList; 068 import java.util.Collections; 069 import java.util.Date; 070 import java.util.HashMap; 071 import java.util.List; 072 import java.util.Map; 073 074 /** 075 * {@link Maintainable} for the {@link AgendaEditor} 076 * 077 * @author Kuali Rice Team (rice.collab@kuali.org) 078 */ 079 public class AgendaEditorMaintainable extends MaintainableImpl { 080 081 private static final long serialVersionUID = 1L; 082 083 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger( 084 AgendaEditorMaintainable.class); 085 086 public static final String NEW_AGENDA_EDITOR_DOCUMENT_TEXT = "New Agenda Editor Document"; 087 088 private transient SequenceAccessorService sequenceAccessorService; 089 090 private transient KrmsRetriever krmsRetriever = new KrmsRetriever(); 091 092 /** 093 * @return the boService 094 */ 095 public BusinessObjectService getBoService() { 096 return KRADServiceLocator.getBusinessObjectService(); 097 } 098 099 /** 100 * return the contextBoService 101 */ 102 private ContextBoService getContextBoService() { 103 return KrmsRepositoryServiceLocator.getContextBoService(); 104 } 105 106 public List<RemotableAttributeField> retrieveAgendaCustomAttributes(View view, Object model, Container container) { 107 AgendaEditor agendaEditor = getAgendaEditor(model); 108 return krmsRetriever.retrieveAgendaCustomAttributes(agendaEditor); 109 } 110 111 /** 112 * Retrieve a list of {@link RemotableAttributeField}s for the parameters (if any) required by the resolver for 113 * the selected term in the proposition that is under edit. 114 */ 115 public List<RemotableAttributeField> retrieveTermParameters(View view, Object model, Container container) { 116 117 List<RemotableAttributeField> results = new ArrayList<RemotableAttributeField>(); 118 119 AgendaEditor agendaEditor = getAgendaEditor(model); 120 121 // Figure out which rule is being edited 122 RuleBo rule = agendaEditor.getAgendaItemLine().getRule(); 123 if (null != rule) { 124 125 // Figure out which proposition is being edited 126 Tree<RuleTreeNode, String> propositionTree = rule.getPropositionTree(); 127 Node<RuleTreeNode, String> editedPropositionNode = findEditedProposition(propositionTree.getRootElement()); 128 129 if (editedPropositionNode != null) { 130 PropositionBo propositionBo = editedPropositionNode.getData().getProposition(); 131 if (StringUtils.isEmpty(propositionBo.getCompoundOpCode()) && CollectionUtils.size( 132 propositionBo.getParameters()) > 0) { 133 // Get the term ID; if it is a new parameterized term, it will have a special prefix 134 PropositionParameterBo param = propositionBo.getParameters().get(0); 135 if (param.getValue().startsWith(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) { 136 String termSpecId = param.getValue().substring( 137 KrmsImplConstants.PARAMETERIZED_TERM_PREFIX.length()); 138 TermResolverDefinition simplestResolver = getSimplestTermResolver(termSpecId, 139 rule.getNamespace()); 140 141 // Get the parameters and build RemotableAttributeFields 142 if (simplestResolver != null) { 143 List<String> parameterNames = new ArrayList<String>(simplestResolver.getParameterNames()); 144 Collections.sort(parameterNames); // make param order deterministic 145 146 for (String parameterName : parameterNames) { 147 // TODO: also allow for DD parameters if there are matching type attributes 148 RemotableTextInput.Builder controlBuilder = RemotableTextInput.Builder.create(); 149 controlBuilder.setSize(64); 150 151 RemotableAttributeField.Builder builder = RemotableAttributeField.Builder.create( 152 parameterName); 153 154 builder.setRequired(true); 155 builder.setDataType(DataType.STRING); 156 builder.setControl(controlBuilder); 157 builder.setLongLabel(parameterName); 158 builder.setShortLabel(parameterName); 159 builder.setMinLength(Integer.valueOf(1)); 160 builder.setMaxLength(Integer.valueOf(64)); 161 162 results.add(builder.build()); 163 } 164 } 165 } 166 } 167 } 168 } 169 return results; 170 } 171 172 /** 173 * finds the term resolver with the fewest parameters that resolves the given term specification 174 * 175 * @param termSpecId the id of the term specification 176 * @param namespace the namespace of the term specification 177 * @return the simples {@link TermResolverDefinition} found, or null if none was found 178 */ 179 // package access so that AgendaEditorController can use it too 180 static TermResolverDefinition getSimplestTermResolver(String termSpecId, 181 String namespace) {// Get the term resolver for the term spec 182 183 List<TermResolverDefinition> resolvers = 184 KrmsRepositoryServiceLocator.getTermBoService().findTermResolversByOutputId(termSpecId, namespace); 185 186 TermResolverDefinition simplestResolver = null; 187 188 for (TermResolverDefinition resolver : resolvers) { 189 if (simplestResolver == null || simplestResolver.getParameterNames().size() < resolver.getParameterNames() 190 .size()) { 191 simplestResolver = resolver; 192 } 193 } 194 195 return simplestResolver; 196 } 197 198 /** 199 * Find and return the node containing the proposition that is in currently in edit mode 200 * 201 * @param node the node to start searching from (typically the root) 202 * @return the node that is currently being edited, if any. Otherwise, null. 203 */ 204 private Node<RuleTreeNode, String> findEditedProposition(Node<RuleTreeNode, String> node) { 205 Node<RuleTreeNode, String> result = null; 206 if (node.getData() != null && node.getData().getProposition() != null && node.getData().getProposition() 207 .getEditMode()) { 208 result = node; 209 } else { 210 for (Node<RuleTreeNode, String> child : node.getChildren()) { 211 result = findEditedProposition(child); 212 if (result != null) { 213 break; 214 } 215 } 216 } 217 return result; 218 } 219 220 /** 221 * Get the AgendaEditor out of the MaintenanceDocumentForm's newMaintainableObject 222 * 223 * @param model the MaintenanceDocumentForm 224 * @return the AgendaEditor 225 */ 226 private AgendaEditor getAgendaEditor(Object model) { 227 MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) model; 228 return (AgendaEditor) maintenanceForm.getDocument().getNewMaintainableObject().getDataObject(); 229 } 230 231 public List<RemotableAttributeField> retrieveRuleActionCustomAttributes(View view, Object model, 232 Container container) { 233 AgendaEditor agendaEditor = getAgendaEditor((MaintenanceDocumentForm) model); 234 return krmsRetriever.retrieveRuleActionCustomAttributes(agendaEditor); 235 } 236 237 /** 238 * This only supports a single action within a rule. 239 */ 240 public List<RemotableAttributeField> retrieveRuleCustomAttributes(View view, Object model, Container container) { 241 AgendaEditor agendaEditor = getAgendaEditor((MaintenanceDocumentForm) model); 242 return krmsRetriever.retrieveRuleCustomAttributes(agendaEditor); 243 } 244 245 @Override 246 public Object retrieveObjectForEditOrCopy(MaintenanceDocument document, Map<String, String> dataObjectKeys) { 247 Object dataObject = null; 248 249 try { 250 // Since the dataObject is a wrapper class we need to build it and populate with the agenda bo. 251 AgendaEditor agendaEditor = new AgendaEditor(); 252 AgendaBo agenda = getLookupService().findObjectBySearch( 253 ((AgendaEditor) getDataObject()).getAgenda().getClass(), dataObjectKeys); 254 if (KRADConstants.MAINTENANCE_COPY_ACTION.equals(getMaintenanceAction())) { 255 String dateTimeStamp = (new Date()).getTime() + ""; 256 String newAgendaName = AgendaItemBo.COPY_OF_TEXT + agenda.getName() + " " + dateTimeStamp; 257 258 AgendaBo copiedAgenda = agenda.copyAgenda(newAgendaName, dateTimeStamp); 259 260 document.getDocumentHeader().setDocumentDescription(NEW_AGENDA_EDITOR_DOCUMENT_TEXT); 261 document.setFieldsClearedOnCopy(true); 262 agendaEditor.setAgenda(copiedAgenda); 263 } else { 264 // set custom attributes map in AgendaEditor 265 // agendaEditor.setCustomAttributesMap(agenda.getAttributes()); 266 agendaEditor.setAgenda(agenda); 267 } 268 agendaEditor.setCustomAttributesMap(agenda.getAttributes()); 269 270 // set extra fields on AgendaEditor 271 agendaEditor.setNamespace(agenda.getContext().getNamespace()); 272 agendaEditor.setContextName(agenda.getContext().getName()); 273 274 dataObject = agendaEditor; 275 } catch (ClassNotPersistenceCapableException ex) { 276 if (!document.getOldMaintainableObject().isExternalBusinessObject()) { 277 throw new RuntimeException("Data Object Class: " 278 + getDataObjectClass() 279 + " is not persistable and is not externalizable - configuration error"); 280 } 281 // otherwise, let fall through 282 } 283 284 return dataObject; 285 } 286 287 /** 288 * Returns the sequenceAssessorService 289 * 290 * @return {@link SequenceAccessorService} 291 */ 292 private SequenceAccessorService getSequenceAccessorService() { 293 if (sequenceAccessorService == null) { 294 sequenceAccessorService = KRADServiceLocator.getSequenceAccessorService(); 295 } 296 return sequenceAccessorService; 297 } 298 299 /** 300 * {@inheritDoc} 301 */ 302 @Override 303 public void processAfterNew(MaintenanceDocument document, Map<String, String[]> requestParameters) { 304 super.processAfterNew(document, requestParameters); 305 document.getDocumentHeader().setDocumentDescription(NEW_AGENDA_EDITOR_DOCUMENT_TEXT); 306 } 307 308 @Override 309 public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> requestParameters) { 310 super.processAfterEdit(document, requestParameters); 311 document.getDocumentHeader().setDocumentDescription("Modify Agenda Editor Document"); 312 } 313 314 @Override 315 public void prepareForSave() { 316 // set agenda attributes 317 AgendaEditor agendaEditor = (AgendaEditor) getDataObject(); 318 agendaEditor.getAgenda().setAttributes(agendaEditor.getCustomAttributesMap()); 319 } 320 321 @Override 322 public void saveDataObject() { 323 AgendaBo agendaBo = ((AgendaEditor) getDataObject()).getAgenda(); 324 325 // handle saving new parameterized terms 326 for (AgendaItemBo agendaItem : agendaBo.getItems()) { 327 PropositionBo propositionBo = agendaItem.getRule().getProposition(); 328 if (propositionBo != null) { 329 saveNewParameterizedTerms(propositionBo); 330 } 331 } 332 333 if (agendaBo instanceof PersistableBusinessObject) { 334 Map<String, String> primaryKeys = new HashMap<String, String>(); 335 primaryKeys.put("id", agendaBo.getId()); 336 AgendaBo dbAgendaBo = getBusinessObjectService().findByPrimaryKey(AgendaBo.class, primaryKeys); 337 getBusinessObjectService().delete(dbAgendaBo); 338 flushCacheBeforeSave(); 339 340 getBusinessObjectService().linkAndSave(agendaBo); 341 } else { 342 throw new RuntimeException("Cannot save object of type: " + agendaBo + " with business object service"); 343 } 344 } 345 346 private void flushCacheBeforeSave(){ 347 //flush krms caches 348 DistributedCacheManagerDecorator distributedCacheManagerDecorator = 349 GlobalResourceLoader.getService(KrmsConstants.KRMS_DISTRIBUTED_CACHE); 350 351 distributedCacheManagerDecorator.getCache(ActionDefinition.Cache.NAME).clear(); 352 distributedCacheManagerDecorator.getCache(AgendaItemDefinition.Cache.NAME).clear(); 353 distributedCacheManagerDecorator.getCache(AgendaTreeDefinition.Cache.NAME).clear(); 354 distributedCacheManagerDecorator.getCache(AgendaDefinition.Cache.NAME).clear(); 355 distributedCacheManagerDecorator.getCache(ContextDefinition.Cache.NAME).clear(); 356 distributedCacheManagerDecorator.getCache(KrmsAttributeDefinition.Cache.NAME).clear(); 357 distributedCacheManagerDecorator.getCache(KrmsTypeDefinition.Cache.NAME).clear(); 358 distributedCacheManagerDecorator.getCache(RuleDefinition.Cache.NAME).clear(); 359 distributedCacheManagerDecorator.getCache(PropositionDefinition.Cache.NAME).clear(); 360 distributedCacheManagerDecorator.getCache(RuleDefinition.Cache.NAME).clear(); 361 distributedCacheManagerDecorator.getCache(TermDefinition.Cache.NAME).clear(); 362 distributedCacheManagerDecorator.getCache(TermResolverDefinition.Cache.NAME).clear(); 363 distributedCacheManagerDecorator.getCache(TermSpecificationDefinition.Cache.NAME).clear(); 364 } 365 366 /** 367 * walk the proposition tree and save any new parameterized terms that are contained therein 368 * 369 * @param propositionBo the root proposition from which to search 370 */ 371 private void saveNewParameterizedTerms(PropositionBo propositionBo) { 372 if (StringUtils.isBlank(propositionBo.getCompoundOpCode())) { 373 // it is a simple proposition 374 if (!propositionBo.getParameters().isEmpty() && propositionBo.getParameters().get(0).getValue().startsWith( 375 KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) { 376 String termId = propositionBo.getParameters().get(0).getValue(); 377 String termSpecId = termId.substring(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX.length()); 378 // create new term 379 TermBo newTerm = new TermBo(); 380 newTerm.setDescription(propositionBo.getNewTermDescription()); 381 newTerm.setSpecificationId(termSpecId); 382 newTerm.setId(KRADServiceLocator.getSequenceAccessorService().getNextAvailableSequenceNumber( 383 KrmsMaintenanceConstants.Sequences.TERM_SPECIFICATION, TermBo.class).toString()); 384 385 List<TermParameterBo> params = new ArrayList<TermParameterBo>(); 386 for (Map.Entry<String, String> entry : propositionBo.getTermParameters().entrySet()) { 387 TermParameterBo param = new TermParameterBo(); 388 param.setTermId(newTerm.getId()); 389 param.setName(entry.getKey()); 390 param.setValue(entry.getValue()); 391 param.setId(KRADServiceLocator.getSequenceAccessorService().getNextAvailableSequenceNumber( 392 KrmsMaintenanceConstants.Sequences.TERM_PARAMETER, TermParameterBo.class).toString()); 393 394 params.add(param); 395 } 396 397 newTerm.setParameters(params); 398 399 KRADServiceLocator.getBusinessObjectService().linkAndSave(newTerm); 400 propositionBo.getParameters().get(0).setValue(newTerm.getId()); 401 } 402 } else { 403 // recurse 404 for (PropositionBo childProp : propositionBo.getCompoundComponents()) { 405 saveNewParameterizedTerms(childProp); 406 } 407 } 408 } 409 410 /** 411 * Build a map from attribute name to attribute definition from all the defined attribute definitions for the 412 * specified agenda type 413 * 414 * @param agendaTypeId 415 * @return 416 */ 417 private Map<String, KrmsAttributeDefinition> buildAttributeDefinitionMap(String agendaTypeId) { 418 KrmsAttributeDefinitionService attributeDefinitionService = 419 KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService(); 420 421 // build a map from attribute name to definition 422 Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>(); 423 424 List<KrmsAttributeDefinition> attributeDefinitions = attributeDefinitionService.findAttributeDefinitionsByType( 425 agendaTypeId); 426 427 for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) { 428 attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition); 429 } 430 return attributeDefinitionMap; 431 } 432 433 @Override 434 public boolean isOldDataObjectInDocument() { 435 boolean isOldDataObjectInExistence = true; 436 437 if (getDataObject() == null) { 438 isOldDataObjectInExistence = false; 439 } else { 440 // dataObject contains a non persistable wrapper - use agenda from the wrapper object instead 441 Map<String, ?> keyFieldValues = getDataObjectMetaDataService().getPrimaryKeyFieldValues( 442 ((AgendaEditor) getDataObject()).getAgenda()); 443 for (Object keyValue : keyFieldValues.values()) { 444 if (keyValue == null) { 445 isOldDataObjectInExistence = false; 446 } else if ((keyValue instanceof String) && StringUtils.isBlank((String) keyValue)) { 447 isOldDataObjectInExistence = false; 448 } 449 450 if (!isOldDataObjectInExistence) { 451 break; 452 } 453 } 454 } 455 456 return isOldDataObjectInExistence; 457 } 458 459 // Since the dataObject is a wrapper class we need to return the agendaBo instead. 460 @Override 461 public Class getDataObjectClass() { 462 return AgendaBo.class; 463 } 464 465 @Override 466 public boolean isLockable() { 467 return true; 468 } 469 470 @Override 471 public PersistableBusinessObject getPersistableBusinessObject() { 472 return ((AgendaEditor) getDataObject()).getAgenda(); 473 } 474 475 @Override 476 protected void processBeforeAddLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) { 477 AgendaEditor agendaEditor = getAgendaEditor(model); 478 if (addLine instanceof ActionBo) { 479 ((ActionBo) addLine).setNamespace(agendaEditor.getAgendaItemLine().getRule().getNamespace()); 480 } 481 482 super.processBeforeAddLine(view, collectionGroup, model, addLine); 483 } 484 }