View Javadoc

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