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