View Javadoc

1   /**
2    * Copyright 2005-2013 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.krms.impl.ui;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
21  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
22  import org.kuali.rice.core.api.uif.DataType;
23  import org.kuali.rice.core.api.uif.RemotableAttributeField;
24  import org.kuali.rice.core.api.uif.RemotableTextInput;
25  import org.kuali.rice.core.api.util.tree.Node;
26  import org.kuali.rice.core.api.util.tree.Tree;
27  import org.kuali.rice.core.impl.cache.DistributedCacheManagerDecorator;
28  import org.kuali.rice.krad.bo.PersistableBusinessObject;
29  import org.kuali.rice.krad.maintenance.Maintainable;
30  import org.kuali.rice.krad.maintenance.MaintainableImpl;
31  import org.kuali.rice.krad.maintenance.MaintenanceDocument;
32  import org.kuali.rice.krad.service.BusinessObjectService;
33  import org.kuali.rice.krad.service.KRADServiceLocator;
34  import org.kuali.rice.krad.service.SequenceAccessorService;
35  import org.kuali.rice.krad.uif.container.CollectionGroup;
36  import org.kuali.rice.krad.uif.container.Container;
37  import org.kuali.rice.krad.uif.view.View;
38  import org.kuali.rice.krad.util.KRADConstants;
39  import org.kuali.rice.krad.web.form.MaintenanceDocumentForm;
40  import org.kuali.rice.krms.api.KrmsConstants;
41  import org.kuali.rice.krms.api.repository.action.ActionDefinition;
42  import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition;
43  import org.kuali.rice.krms.api.repository.agenda.AgendaItemDefinition;
44  import org.kuali.rice.krms.api.repository.agenda.AgendaTreeDefinition;
45  import org.kuali.rice.krms.api.repository.context.ContextDefinition;
46  import org.kuali.rice.krms.api.repository.function.FunctionDefinition;
47  import org.kuali.rice.krms.api.repository.operator.CustomOperator;
48  import org.kuali.rice.krms.api.repository.proposition.PropositionDefinition;
49  import org.kuali.rice.krms.api.repository.proposition.PropositionParameterType;
50  import org.kuali.rice.krms.api.repository.rule.RuleDefinition;
51  import org.kuali.rice.krms.api.repository.term.TermDefinition;
52  import org.kuali.rice.krms.api.repository.term.TermResolverDefinition;
53  import org.kuali.rice.krms.api.repository.term.TermSpecificationDefinition;
54  import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition;
55  import org.kuali.rice.krms.api.repository.type.KrmsTypeDefinition;
56  import org.kuali.rice.krms.impl.repository.ActionBo;
57  import org.kuali.rice.krms.impl.repository.AgendaBo;
58  import org.kuali.rice.krms.impl.repository.AgendaItemBo;
59  import org.kuali.rice.krms.impl.repository.ContextBoService;
60  import org.kuali.rice.krms.impl.repository.KrmsAttributeDefinitionService;
61  import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator;
62  import org.kuali.rice.krms.impl.repository.PropositionBo;
63  import org.kuali.rice.krms.impl.repository.PropositionParameterBo;
64  import org.kuali.rice.krms.impl.repository.RuleBo;
65  import org.kuali.rice.krms.impl.repository.TermBo;
66  import org.kuali.rice.krms.impl.repository.TermParameterBo;
67  import org.kuali.rice.krms.impl.util.KrmsImplConstants;
68  import org.kuali.rice.krms.impl.util.KrmsRetriever;
69  import org.kuali.rice.krms.impl.util.KrmsServiceLocatorInternal;
70  
71  import java.util.ArrayList;
72  import java.util.Collections;
73  import java.util.Date;
74  import java.util.HashMap;
75  import java.util.List;
76  import java.util.Map;
77  
78  /**
79   * {@link Maintainable} for the {@link AgendaEditor}
80   *
81   * @author Kuali Rice Team (rice.collab@kuali.org)
82   */
83  public class AgendaEditorMaintainable extends MaintainableImpl {
84  
85      private static final long serialVersionUID = 1L;
86  
87      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
88              AgendaEditorMaintainable.class);
89  
90      public static final String NEW_AGENDA_EDITOR_DOCUMENT_TEXT = "New Agenda Editor Document";
91  
92      private transient SequenceAccessorService sequenceAccessorService;
93  
94      private transient KrmsRetriever krmsRetriever = new KrmsRetriever();
95  
96      /**
97       * @return the boService
98       */
99      public BusinessObjectService getBoService() {
100         return KRADServiceLocator.getBusinessObjectService();
101     }
102 
103     /**
104      * return the contextBoService
105      */
106     private ContextBoService getContextBoService() {
107         return KrmsRepositoryServiceLocator.getContextBoService();
108     }
109 
110     public List<RemotableAttributeField> retrieveAgendaCustomAttributes(View view, Object model, Container container) {
111         AgendaEditor agendaEditor = getAgendaEditor(model);
112         return krmsRetriever.retrieveAgendaCustomAttributes(agendaEditor);
113     }
114 
115     /**
116      * Retrieve a list of {@link RemotableAttributeField}s for the parameters (if any) required by the resolver for
117      * the selected term in the proposition that is under edit.
118      */
119     public List<RemotableAttributeField> retrieveTermParameters(View view, Object model, Container container) {
120 
121         List<RemotableAttributeField> results = new ArrayList<RemotableAttributeField>();
122 
123         AgendaEditor agendaEditor = getAgendaEditor(model);
124 
125         // Figure out which rule is being edited
126         RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
127         if (null != rule) {
128 
129             // Figure out which proposition is being edited
130             Tree<RuleTreeNode, String> propositionTree = rule.getPropositionTree();
131             Node<RuleTreeNode, String> editedPropositionNode = findEditedProposition(propositionTree.getRootElement());
132 
133             if (editedPropositionNode != null) {
134                 PropositionBo propositionBo = editedPropositionNode.getData().getProposition();
135                 if (StringUtils.isEmpty(propositionBo.getCompoundOpCode()) && CollectionUtils.size(
136                         propositionBo.getParameters()) > 0) {
137                     // Get the term ID; if it is a new parameterized term, it will have a special prefix
138                     PropositionParameterBo param = propositionBo.getParameters().get(0);
139                     if (param.getValue().startsWith(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) {
140                         String termSpecId = param.getValue().substring(
141                                 KrmsImplConstants.PARAMETERIZED_TERM_PREFIX.length());
142                         TermResolverDefinition simplestResolver = getSimplestTermResolver(termSpecId,
143                                 rule.getNamespace());
144 
145                         // Get the parameters and build RemotableAttributeFields
146                         if (simplestResolver != null) {
147                             List<String> parameterNames = new ArrayList<String>(simplestResolver.getParameterNames());
148                             Collections.sort(parameterNames); // make param order deterministic
149 
150                             for (String parameterName : parameterNames) {
151                                 // TODO: also allow for DD parameters if there are matching type attributes
152                                 RemotableTextInput.Builder controlBuilder = RemotableTextInput.Builder.create();
153                                 controlBuilder.setSize(64);
154 
155                                 RemotableAttributeField.Builder builder = RemotableAttributeField.Builder.create(
156                                         parameterName);
157 
158                                 builder.setRequired(true);
159                                 builder.setDataType(DataType.STRING);
160                                 builder.setControl(controlBuilder);
161                                 builder.setLongLabel(parameterName);
162                                 builder.setShortLabel(parameterName);
163                                 builder.setMinLength(Integer.valueOf(1));
164                                 builder.setMaxLength(Integer.valueOf(64));
165 
166                                 results.add(builder.build());
167                             }
168                         }
169                     }
170                 }
171             }
172         }
173         return results;
174     }
175 
176     /**
177      * finds the term resolver with the fewest parameters that resolves the given term specification
178      *
179      * @param termSpecId the id of the term specification
180      * @param namespace the  namespace of the term specification
181      * @return the simples {@link TermResolverDefinition} found, or null if none was found
182      */
183     // package access so that AgendaEditorController can use it too
184     static TermResolverDefinition getSimplestTermResolver(String termSpecId,
185             String namespace) {// Get the term resolver for the term spec
186 
187         List<TermResolverDefinition> resolvers =
188                 KrmsRepositoryServiceLocator.getTermBoService().findTermResolversByOutputId(termSpecId, namespace);
189 
190         TermResolverDefinition simplestResolver = null;
191 
192         for (TermResolverDefinition resolver : resolvers) {
193             if (simplestResolver == null || simplestResolver.getParameterNames().size() < resolver.getParameterNames()
194                     .size()) {
195                 simplestResolver = resolver;
196             }
197         }
198 
199         return simplestResolver;
200     }
201 
202     /**
203      * Find and return the node containing the proposition that is in currently in edit mode
204      *
205      * @param node the node to start searching from (typically the root)
206      * @return the node that is currently being edited, if any.  Otherwise, null.
207      */
208     private Node<RuleTreeNode, String> findEditedProposition(Node<RuleTreeNode, String> node) {
209         Node<RuleTreeNode, String> result = null;
210         if (node.getData() != null && node.getData().getProposition() != null && node.getData().getProposition()
211                 .getEditMode()) {
212             result = node;
213         } else {
214             for (Node<RuleTreeNode, String> child : node.getChildren()) {
215                 result = findEditedProposition(child);
216                 if (result != null) {
217                     break;
218                 }
219             }
220         }
221         return result;
222     }
223 
224     /**
225      * Get the AgendaEditor out of the MaintenanceDocumentForm's newMaintainableObject
226      *
227      * @param model the MaintenanceDocumentForm
228      * @return the AgendaEditor
229      */
230     private AgendaEditor getAgendaEditor(Object model) {
231         MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) model;
232         return (AgendaEditor) maintenanceForm.getDocument().getNewMaintainableObject().getDataObject();
233     }
234 
235     public List<RemotableAttributeField> retrieveRuleActionCustomAttributes(View view, Object model,
236             Container container) {
237         AgendaEditor agendaEditor = getAgendaEditor((MaintenanceDocumentForm) model);
238         return krmsRetriever.retrieveRuleActionCustomAttributes(agendaEditor);
239     }
240 
241     /**
242      * This only supports a single action within a rule.
243      */
244     public List<RemotableAttributeField> retrieveRuleCustomAttributes(View view, Object model, Container container) {
245         AgendaEditor agendaEditor = getAgendaEditor((MaintenanceDocumentForm) model);
246         return krmsRetriever.retrieveRuleCustomAttributes(agendaEditor);
247     }
248 
249     @Override
250     public Object retrieveObjectForEditOrCopy(MaintenanceDocument document, Map<String, String> dataObjectKeys) {
251         Object dataObject = null;
252 
253         try {
254             // Since the dataObject is a wrapper class we need to build it and populate with the agenda bo.
255             AgendaEditor agendaEditor = new AgendaEditor();
256             AgendaBo agenda = getLookupService().findObjectBySearch(
257                     ((AgendaEditor) getDataObject()).getAgenda().getClass(), dataObjectKeys);
258             if (KRADConstants.MAINTENANCE_COPY_ACTION.equals(getMaintenanceAction())) {
259                 String dateTimeStamp = (new Date()).getTime() + "";
260                 String newAgendaName = AgendaItemBo.COPY_OF_TEXT + agenda.getName() + " " + dateTimeStamp;
261 
262                 AgendaBo copiedAgenda = agenda.copyAgenda(newAgendaName, dateTimeStamp);
263 
264                 document.getDocumentHeader().setDocumentDescription(NEW_AGENDA_EDITOR_DOCUMENT_TEXT);
265                 document.setFieldsClearedOnCopy(true);
266                 agendaEditor.setAgenda(copiedAgenda);
267             } else {
268                 // set custom attributes map in AgendaEditor
269                 //                agendaEditor.setCustomAttributesMap(agenda.getAttributes());
270                 agendaEditor.setAgenda(agenda);
271             }
272             agendaEditor.setCustomAttributesMap(agenda.getAttributes());
273 
274             // set extra fields on AgendaEditor
275             agendaEditor.setNamespace(agenda.getContext().getNamespace());
276             agendaEditor.setContextName(agenda.getContext().getName());
277 
278             dataObject = agendaEditor;
279         } catch (ClassNotPersistenceCapableException ex) {
280             if (!document.getOldMaintainableObject().isExternalBusinessObject()) {
281                 throw new RuntimeException("Data Object Class: "
282                         + getDataObjectClass()
283                         + " is not persistable and is not externalizable - configuration error");
284             }
285             // otherwise, let fall through
286         }
287 
288         return dataObject;
289     }
290 
291     /**
292      * Returns the sequenceAssessorService
293      *
294      * @return {@link SequenceAccessorService}
295      */
296     private SequenceAccessorService getSequenceAccessorService() {
297         if (sequenceAccessorService == null) {
298             sequenceAccessorService = KRADServiceLocator.getSequenceAccessorService();
299         }
300         return sequenceAccessorService;
301     }
302 
303     /**
304      * {@inheritDoc}
305      */
306     @Override
307     public void processAfterNew(MaintenanceDocument document, Map<String, String[]> requestParameters) {
308         super.processAfterNew(document, requestParameters);
309         document.getDocumentHeader().setDocumentDescription(NEW_AGENDA_EDITOR_DOCUMENT_TEXT);
310     }
311 
312     @Override
313     public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> requestParameters) {
314         super.processAfterEdit(document, requestParameters);
315         document.getDocumentHeader().setDocumentDescription("Modify Agenda Editor Document");
316     }
317 
318     @Override
319     public void prepareForSave() {
320         // set agenda attributes
321         AgendaEditor agendaEditor = (AgendaEditor) getDataObject();
322         agendaEditor.getAgenda().setAttributes(agendaEditor.getCustomAttributesMap());
323     }
324 
325     @Override
326     public void saveDataObject() {
327         AgendaBo agendaBo = ((AgendaEditor) getDataObject()).getAgenda();
328 
329         // handle saving new parameterized terms and processing custom operators
330         for (AgendaItemBo agendaItem : agendaBo.getItems()) {
331             PropositionBo propositionBo = agendaItem.getRule().getProposition();
332             if (propositionBo != null) {
333                 saveNewParameterizedTerms(propositionBo);
334                 processCustomOperators(propositionBo);
335             }
336         }
337 
338         if (agendaBo instanceof PersistableBusinessObject) {
339             Map<String, String> primaryKeys = new HashMap<String, String>();
340             primaryKeys.put("id", agendaBo.getId());
341             AgendaBo dbAgendaBo = getBusinessObjectService().findByPrimaryKey(AgendaBo.class, primaryKeys);
342             getBusinessObjectService().delete(dbAgendaBo);
343             flushCacheBeforeSave();
344 
345             getBusinessObjectService().linkAndSave(agendaBo);
346         } else {
347             throw new RuntimeException("Cannot save object of type: " + agendaBo + " with business object service");
348         }
349     }
350 
351     private void flushCacheBeforeSave(){
352         //flush krms caches
353         DistributedCacheManagerDecorator distributedCacheManagerDecorator =
354                 GlobalResourceLoader.getService(KrmsConstants.KRMS_DISTRIBUTED_CACHE);
355 
356         distributedCacheManagerDecorator.getCache(ActionDefinition.Cache.NAME).clear();
357         distributedCacheManagerDecorator.getCache(AgendaItemDefinition.Cache.NAME).clear();
358         distributedCacheManagerDecorator.getCache(AgendaTreeDefinition.Cache.NAME).clear();
359         distributedCacheManagerDecorator.getCache(AgendaDefinition.Cache.NAME).clear();
360         distributedCacheManagerDecorator.getCache(ContextDefinition.Cache.NAME).clear();
361         distributedCacheManagerDecorator.getCache(KrmsAttributeDefinition.Cache.NAME).clear();
362         distributedCacheManagerDecorator.getCache(KrmsTypeDefinition.Cache.NAME).clear();
363         distributedCacheManagerDecorator.getCache(RuleDefinition.Cache.NAME).clear();
364         distributedCacheManagerDecorator.getCache(PropositionDefinition.Cache.NAME).clear();
365         distributedCacheManagerDecorator.getCache(RuleDefinition.Cache.NAME).clear();
366         distributedCacheManagerDecorator.getCache(TermDefinition.Cache.NAME).clear();
367         distributedCacheManagerDecorator.getCache(TermResolverDefinition.Cache.NAME).clear();
368         distributedCacheManagerDecorator.getCache(TermSpecificationDefinition.Cache.NAME).clear();
369     }
370 
371     /**
372      * walk the proposition tree and save any new parameterized terms that are contained therein
373      *
374      * @param propositionBo the root proposition from which to search
375      */
376     private void saveNewParameterizedTerms(PropositionBo propositionBo) {
377         if (StringUtils.isBlank(propositionBo.getCompoundOpCode())) {
378             // it is a simple proposition
379             if (!propositionBo.getParameters().isEmpty() && propositionBo.getParameters().get(0).getValue().startsWith(
380                     KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) {
381                 String termId = propositionBo.getParameters().get(0).getValue();
382                 String termSpecId = termId.substring(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX.length());
383                 // create new term
384                 TermBo newTerm = new TermBo();
385                 newTerm.setDescription(propositionBo.getNewTermDescription());
386                 newTerm.setSpecificationId(termSpecId);
387                 newTerm.setId(KRADServiceLocator.getSequenceAccessorService().getNextAvailableSequenceNumber(
388                         KrmsMaintenanceConstants.Sequences.TERM_SPECIFICATION, TermBo.class).toString());
389 
390                 List<TermParameterBo> params = new ArrayList<TermParameterBo>();
391                 for (Map.Entry<String, String> entry : propositionBo.getTermParameters().entrySet()) {
392                     TermParameterBo param = new TermParameterBo();
393                     param.setTermId(newTerm.getId());
394                     param.setName(entry.getKey());
395                     param.setValue(entry.getValue());
396                     param.setId(KRADServiceLocator.getSequenceAccessorService().getNextAvailableSequenceNumber(
397                             KrmsMaintenanceConstants.Sequences.TERM_PARAMETER, TermParameterBo.class).toString());
398 
399                     params.add(param);
400                 }
401 
402                 newTerm.setParameters(params);
403 
404                 KRADServiceLocator.getBusinessObjectService().linkAndSave(newTerm);
405                 propositionBo.getParameters().get(0).setValue(newTerm.getId());
406             }
407         } else {
408             // recurse
409             for (PropositionBo childProp : propositionBo.getCompoundComponents()) {
410                 saveNewParameterizedTerms(childProp);
411             }
412         }
413     }
414 
415     /**
416      * walk the proposition tree and process any custom operators found, converting them to custom function invocations.
417      *
418      * @param propositionBo the root proposition from which to search and convert
419      */
420     private void processCustomOperators(PropositionBo propositionBo) {
421         if (StringUtils.isBlank(propositionBo.getCompoundOpCode())) {
422             // if it is a simple proposition with a custom operator
423             if (!propositionBo.getParameters().isEmpty() && propositionBo.getParameters().get(2).getValue().startsWith(
424                     KrmsImplConstants.CUSTOM_OPERATOR_PREFIX)) {
425                 PropositionParameterBo operatorParam = propositionBo.getParameters().get(2);
426 
427                 CustomOperator customOperator =
428                         KrmsServiceLocatorInternal.getCustomOperatorUiTranslator().getCustomOperator(operatorParam.getValue());
429 
430                 FunctionDefinition operatorFunctionDefinition = customOperator.getOperatorFunctionDefinition();
431 
432                 operatorParam.setParameterType(PropositionParameterType.FUNCTION.getCode());
433                 operatorParam.setValue(operatorFunctionDefinition.getId());
434             }
435         } else {
436             // recurse
437             for (PropositionBo childProp : propositionBo.getCompoundComponents()) {
438                 processCustomOperators(childProp);
439             }
440         }
441     }
442 
443     /**
444      * Build a map from attribute name to attribute definition from all the defined attribute definitions for the
445      * specified agenda type
446      *
447      * @param agendaTypeId
448      * @return
449      */
450     private Map<String, KrmsAttributeDefinition> buildAttributeDefinitionMap(String agendaTypeId) {
451         KrmsAttributeDefinitionService attributeDefinitionService =
452                 KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService();
453 
454         // build a map from attribute name to definition
455         Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>();
456 
457         List<KrmsAttributeDefinition> attributeDefinitions = attributeDefinitionService.findAttributeDefinitionsByType(
458                 agendaTypeId);
459 
460         for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) {
461             attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition);
462         }
463         return attributeDefinitionMap;
464     }
465 
466     @Override
467     public boolean isOldDataObjectInDocument() {
468         boolean isOldDataObjectInExistence = true;
469 
470         if (getDataObject() == null) {
471             isOldDataObjectInExistence = false;
472         } else {
473             // dataObject contains a non persistable wrapper - use agenda from the wrapper object instead
474             Map<String, ?> keyFieldValues = getDataObjectMetaDataService().getPrimaryKeyFieldValues(
475                     ((AgendaEditor) getDataObject()).getAgenda());
476             for (Object keyValue : keyFieldValues.values()) {
477                 if (keyValue == null) {
478                     isOldDataObjectInExistence = false;
479                 } else if ((keyValue instanceof String) && StringUtils.isBlank((String) keyValue)) {
480                     isOldDataObjectInExistence = false;
481                 }
482 
483                 if (!isOldDataObjectInExistence) {
484                     break;
485                 }
486             }
487         }
488 
489         return isOldDataObjectInExistence;
490     }
491 
492     // Since the dataObject is a wrapper class we need to return the agendaBo instead.
493     @Override
494     public Class getDataObjectClass() {
495         return AgendaBo.class;
496     }
497 
498     @Override
499     public boolean isLockable() {
500         return true;
501     }
502 
503     @Override
504     public PersistableBusinessObject getPersistableBusinessObject() {
505         return ((AgendaEditor) getDataObject()).getAgenda();
506     }
507 
508     @Override
509     protected void processBeforeAddLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) {
510         AgendaEditor agendaEditor = getAgendaEditor(model);
511         if (addLine instanceof ActionBo) {
512             ((ActionBo) addLine).setNamespace(agendaEditor.getAgendaItemLine().getRule().getNamespace());
513         }
514 
515         super.processBeforeAddLine(view, collectionGroup, model, addLine);
516     }
517 }