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.uif.DataType;
22  import org.kuali.rice.core.api.uif.RemotableAttributeField;
23  import org.kuali.rice.core.api.uif.RemotableTextInput;
24  import org.kuali.rice.core.api.util.tree.Node;
25  import org.kuali.rice.core.api.util.tree.Tree;
26  import org.kuali.rice.krad.bo.PersistableBusinessObject;
27  import org.kuali.rice.krad.maintenance.MaintenanceDocument;
28  import org.kuali.rice.krad.maintenance.Maintainable;
29  import org.kuali.rice.krad.maintenance.MaintainableImpl;
30  import org.kuali.rice.krad.service.BusinessObjectService;
31  import org.kuali.rice.krad.service.KRADServiceLocator;
32  import org.kuali.rice.krad.service.SequenceAccessorService;
33  import org.kuali.rice.krad.uif.container.CollectionGroup;
34  import org.kuali.rice.krad.uif.container.Container;
35  import org.kuali.rice.krad.uif.view.View;
36  import org.kuali.rice.krad.util.KRADConstants;
37  import org.kuali.rice.krad.web.form.MaintenanceDocumentForm;
38  import org.kuali.rice.krms.api.repository.term.TermResolverDefinition;
39  import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition;
40  import org.kuali.rice.krms.impl.repository.ActionBo;
41  import org.kuali.rice.krms.impl.repository.AgendaBo;
42  import org.kuali.rice.krms.impl.repository.AgendaItemBo;
43  import org.kuali.rice.krms.impl.repository.ContextBoService;
44  import org.kuali.rice.krms.impl.repository.KrmsAttributeDefinitionService;
45  import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator;
46  import org.kuali.rice.krms.impl.repository.PropositionBo;
47  import org.kuali.rice.krms.impl.repository.PropositionParameterBo;
48  import org.kuali.rice.krms.impl.repository.RuleBo;
49  import org.kuali.rice.krms.impl.repository.TermBo;
50  import org.kuali.rice.krms.impl.repository.TermParameterBo;
51  import org.kuali.rice.krms.impl.util.KrmsImplConstants;
52  import org.kuali.rice.krms.impl.util.KrmsRetriever;
53  
54  import java.util.ArrayList;
55  import java.util.Collections;
56  import java.util.Date;
57  import java.util.HashMap;
58  import java.util.List;
59  import java.util.Map;
60  
61  /**
62   * {@link Maintainable} for the {@link AgendaEditor}
63   *
64   * @author Kuali Rice Team (rice.collab@kuali.org)
65   */
66  public class AgendaEditorMaintainable extends MaintainableImpl {
67  
68      private static final long serialVersionUID = 1L;
69  
70      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
71              AgendaEditorMaintainable.class);
72  
73      public static final String NEW_AGENDA_EDITOR_DOCUMENT_TEXT = "New Agenda Editor Document";
74  
75      private transient SequenceAccessorService sequenceAccessorService;
76  
77      private transient KrmsRetriever krmsRetriever = new KrmsRetriever();
78  
79      /**
80       * @return the boService
81       */
82      public BusinessObjectService getBoService() {
83          return KRADServiceLocator.getBusinessObjectService();
84      }
85  
86      /**
87       * return the contextBoService
88       */
89      private ContextBoService getContextBoService() {
90          return KrmsRepositoryServiceLocator.getContextBoService();
91      }
92  
93      public List<RemotableAttributeField> retrieveAgendaCustomAttributes(View view, Object model, Container container) {
94          AgendaEditor agendaEditor = getAgendaEditor(model);
95          return krmsRetriever.retrieveAgendaCustomAttributes(agendaEditor);
96      }
97  
98      /**
99       * 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 }