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