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