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