001/**
002 * Copyright 2005-2017 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krms.impl.ui;
017
018import org.apache.commons.collections.CollectionUtils;
019import org.apache.commons.lang.StringUtils;
020import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
021import org.kuali.rice.core.api.data.DataType;
022import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
023import org.kuali.rice.core.api.uif.RemotableAttributeField;
024import org.kuali.rice.core.api.uif.RemotableTextInput;
025import org.kuali.rice.core.api.util.tree.Node;
026import org.kuali.rice.core.api.util.tree.Tree;
027import org.kuali.rice.core.impl.cache.DistributedCacheManagerDecorator;
028import org.kuali.rice.krad.data.PersistenceOption;
029import org.kuali.rice.krad.maintenance.Maintainable;
030import org.kuali.rice.krad.maintenance.MaintainableImpl;
031import org.kuali.rice.krad.maintenance.MaintenanceDocument;
032import org.kuali.rice.krad.uif.container.Container;
033import org.kuali.rice.krad.uif.view.View;
034import org.kuali.rice.krad.uif.view.ViewModel;
035import org.kuali.rice.krad.util.KRADConstants;
036import org.kuali.rice.krad.util.ObjectUtils;
037import org.kuali.rice.krad.web.form.MaintenanceDocumentForm;
038import org.kuali.rice.krms.api.KrmsConstants;
039import org.kuali.rice.krms.api.repository.action.ActionDefinition;
040import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition;
041import org.kuali.rice.krms.api.repository.agenda.AgendaItemDefinition;
042import org.kuali.rice.krms.api.repository.agenda.AgendaTreeDefinition;
043import org.kuali.rice.krms.api.repository.context.ContextDefinition;
044import org.kuali.rice.krms.api.repository.function.FunctionDefinition;
045import org.kuali.rice.krms.api.repository.operator.CustomOperator;
046import org.kuali.rice.krms.api.repository.proposition.PropositionDefinition;
047import org.kuali.rice.krms.api.repository.proposition.PropositionParameterType;
048import org.kuali.rice.krms.api.repository.rule.RuleDefinition;
049import org.kuali.rice.krms.api.repository.term.TermDefinition;
050import org.kuali.rice.krms.api.repository.term.TermResolverDefinition;
051import org.kuali.rice.krms.api.repository.term.TermSpecificationDefinition;
052import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition;
053import org.kuali.rice.krms.api.repository.type.KrmsTypeDefinition;
054import org.kuali.rice.krms.impl.repository.ActionAttributeBo;
055import org.kuali.rice.krms.impl.repository.ActionBo;
056import org.kuali.rice.krms.impl.repository.AgendaBo;
057import org.kuali.rice.krms.impl.repository.AgendaItemBo;
058import org.kuali.rice.krms.impl.repository.ContextBoService;
059import org.kuali.rice.krms.impl.repository.KrmsAttributeDefinitionService;
060import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator;
061import org.kuali.rice.krms.impl.repository.PropositionBo;
062import org.kuali.rice.krms.impl.repository.PropositionParameterBo;
063import org.kuali.rice.krms.impl.repository.RepositoryBoIncrementer;
064import org.kuali.rice.krms.impl.repository.RuleAttributeBo;
065import org.kuali.rice.krms.impl.repository.RuleBo;
066import org.kuali.rice.krms.impl.repository.TermBo;
067import org.kuali.rice.krms.impl.repository.TermParameterBo;
068import org.kuali.rice.krms.impl.util.KrmsImplConstants;
069import org.kuali.rice.krms.impl.util.KrmsRetriever;
070import org.kuali.rice.krms.impl.util.KrmsServiceLocatorInternal;
071
072import java.util.ArrayList;
073import java.util.Collections;
074import java.util.Date;
075import java.util.HashMap;
076import java.util.List;
077import java.util.Map;
078
079import static org.kuali.rice.krms.impl.repository.BusinessObjectServiceMigrationUtils.findSingleMatching;
080
081/**
082 * {@link Maintainable} for the {@link AgendaEditor}
083 *
084 * @author Kuali Rice Team (rice.collab@kuali.org)
085 */
086public class AgendaEditorMaintainable extends MaintainableImpl {
087
088    private static final long serialVersionUID = 1L;
089
090    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
091            AgendaEditorMaintainable.class);
092
093    public static final String NEW_AGENDA_EDITOR_DOCUMENT_TEXT = "New Agenda Editor Document";
094    private static final RepositoryBoIncrementer termIdIncrementer = new RepositoryBoIncrementer(TermBo.TERM_SEQ_NAME);
095    private static final RepositoryBoIncrementer termParameterIdIncrementer = new RepositoryBoIncrementer(TermParameterBo.TERM_PARM_SEQ_NAME);
096    private static final RepositoryBoIncrementer agendaItemIncrementer = new RepositoryBoIncrementer(AgendaBo.AGENDA_SEQ_NAME);
097
098    private transient KrmsRetriever krmsRetriever = new KrmsRetriever();
099
100    /**
101     * return the contextBoService
102     */
103    private ContextBoService getContextBoService() {
104        return KrmsRepositoryServiceLocator.getContextBoService();
105    }
106
107    public List<RemotableAttributeField> retrieveAgendaCustomAttributes(View view, Object model, Container container) {
108        AgendaEditor agendaEditor = getAgendaEditor(model);
109        return krmsRetriever.retrieveAgendaCustomAttributes(agendaEditor);
110    }
111
112    /**
113     * Retrieve a list of {@link RemotableAttributeField}s for the parameters (if any) required by the resolver for
114     * the selected term in the proposition that is under edit.
115     */
116    public List<RemotableAttributeField> retrieveTermParameters(View view, Object model, Container container) {
117
118        List<RemotableAttributeField> results = new ArrayList<RemotableAttributeField>();
119
120        AgendaEditor agendaEditor = getAgendaEditor(model);
121
122        // Figure out which rule is being edited
123        RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
124        if (null != rule) {
125
126            // Figure out which proposition is being edited
127            Tree<RuleTreeNode, String> propositionTree = rule.getPropositionTree();
128            Node<RuleTreeNode, String> editedPropositionNode = findEditedProposition(propositionTree.getRootElement());
129
130            if (editedPropositionNode != null) {
131                PropositionBo propositionBo = editedPropositionNode.getData().getProposition();
132                if (StringUtils.isEmpty(propositionBo.getCompoundOpCode()) && CollectionUtils.size(
133                        propositionBo.getParameters()) > 0) {
134                    // Get the term ID; if it is a new parameterized term, it will have a special prefix
135                    PropositionParameterBo param = propositionBo.getParameters().get(0);
136                    if (StringUtils.isNotBlank(param.getValue()) &&
137                            param.getValue().startsWith(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) {
138                        String termSpecId = param.getValue().substring(
139                                KrmsImplConstants.PARAMETERIZED_TERM_PREFIX.length());
140                        TermResolverDefinition simplestResolver = getSimplestTermResolver(termSpecId,
141                                rule.getNamespace());
142
143                        // Get the parameters and build RemotableAttributeFields
144                        if (simplestResolver != null) {
145                            List<String> parameterNames = new ArrayList<String>(simplestResolver.getParameterNames());
146                            Collections.sort(parameterNames); // make param order deterministic
147
148                            for (String parameterName : parameterNames) {
149                                // TODO: also allow for DD parameters if there are matching type attributes
150                                RemotableTextInput.Builder controlBuilder = RemotableTextInput.Builder.create();
151                                controlBuilder.setSize(64);
152
153                                RemotableAttributeField.Builder builder = RemotableAttributeField.Builder.create(
154                                        parameterName);
155
156                                builder.setRequired(true);
157                                builder.setDataType(DataType.STRING);
158                                builder.setControl(controlBuilder);
159                                builder.setLongLabel(parameterName);
160                                builder.setShortLabel(parameterName);
161                                builder.setMinLength(Integer.valueOf(1));
162                                builder.setMaxLength(Integer.valueOf(64));
163
164                                results.add(builder.build());
165                            }
166                        }
167                    }
168                }
169            }
170        }
171        return results;
172    }
173
174    /**
175     * finds the term resolver with the fewest parameters that resolves the given term specification
176     *
177     * @param termSpecId the id of the term specification
178     * @param namespace the  namespace of the term specification
179     * @return the simples {@link TermResolverDefinition} found, or null if none was found
180     */
181    // package access so that AgendaEditorController can use it too
182    static TermResolverDefinition getSimplestTermResolver(String termSpecId,
183            String namespace) {// Get the term resolver for the term spec
184
185        List<TermResolverDefinition> resolvers =
186                KrmsRepositoryServiceLocator.getTermBoService().findTermResolversByOutputId(termSpecId, namespace);
187
188        TermResolverDefinition simplestResolver = null;
189
190        for (TermResolverDefinition resolver : resolvers) {
191            if (simplestResolver == null || simplestResolver.getParameterNames().size() < resolver.getParameterNames()
192                    .size()) {
193                simplestResolver = resolver;
194            }
195        }
196
197        return simplestResolver;
198    }
199
200    /**
201     * Find and return the node containing the proposition that is in currently in edit mode
202     *
203     * @param node the node to start searching from (typically the root)
204     * @return the node that is currently being edited, if any.  Otherwise, null.
205     */
206    private Node<RuleTreeNode, String> findEditedProposition(Node<RuleTreeNode, String> node) {
207        Node<RuleTreeNode, String> result = null;
208        if (node.getData() != null && node.getData().getProposition() != null && node.getData().getProposition()
209                .getEditMode()) {
210            result = node;
211        } else {
212            for (Node<RuleTreeNode, String> child : node.getChildren()) {
213                result = findEditedProposition(child);
214                if (result != null) {
215                    break;
216                }
217            }
218        }
219        return result;
220    }
221
222    /**
223     * Get the AgendaEditor out of the MaintenanceDocumentForm's newMaintainableObject
224     *
225     * @param model the MaintenanceDocumentForm
226     * @return the AgendaEditor
227     */
228    private AgendaEditor getAgendaEditor(Object model) {
229        MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) model;
230        return (AgendaEditor) maintenanceForm.getDocument().getNewMaintainableObject().getDataObject();
231    }
232
233    public List<RemotableAttributeField> retrieveRuleActionCustomAttributes(View view, Object model,
234            Container container) {
235        AgendaEditor agendaEditor = getAgendaEditor((MaintenanceDocumentForm) model);
236        return krmsRetriever.retrieveRuleActionCustomAttributes(agendaEditor);
237    }
238
239    /**
240     * This only supports a single action within a rule.
241     */
242    public List<RemotableAttributeField> retrieveRuleCustomAttributes(View view, Object model, Container container) {
243        AgendaEditor agendaEditor = getAgendaEditor((MaintenanceDocumentForm) model);
244        return krmsRetriever.retrieveRuleCustomAttributes(agendaEditor);
245    }
246
247    @Override
248    public Object retrieveObjectForEditOrCopy(MaintenanceDocument document, Map<String, String> dataObjectKeys) {
249        Object dataObject = null;
250
251        try {
252            // Since the dataObject is a wrapper class we need to build it and populate with the agenda bo.
253            AgendaEditor agendaEditor = new AgendaEditor();
254            AgendaBo agenda = findSingleMatching(getDataObjectService(),
255                    ((AgendaEditor) getDataObject()).getAgenda().getClass(), dataObjectKeys);
256
257            // HACK: force lazy loaded items to be fetched
258            forceLoadLazyRelations(agenda);
259
260            if (KRADConstants.MAINTENANCE_COPY_ACTION.equals(getMaintenanceAction())) {
261                String dateTimeStamp = (new Date()).getTime() + "";
262                String newAgendaName = AgendaItemBo.COPY_OF_TEXT + agenda.getName() + " " + dateTimeStamp;
263
264                AgendaBo copiedAgenda = agenda.copyAgenda(newAgendaName, dateTimeStamp);
265
266                document.getDocumentHeader().setDocumentDescription(NEW_AGENDA_EDITOR_DOCUMENT_TEXT);
267                document.setFieldsClearedOnCopy(true);
268                agendaEditor.setAgenda(copiedAgenda);
269            } else {
270                // set custom attributes map in AgendaEditor
271                //                agendaEditor.setCustomAttributesMap(agenda.getAttributes());
272                agendaEditor.setAgenda(agenda);
273            }
274            agendaEditor.setCustomAttributesMap(agenda.getAttributes());
275
276            // set extra fields on AgendaEditor
277            agendaEditor.setNamespace(agenda.getContext().getNamespace());
278            agendaEditor.setContextName(agenda.getContext().getName());
279
280            dataObject = agendaEditor;
281        } catch (ClassNotPersistenceCapableException ex) {
282            if (!document.getOldMaintainableObject().isExternalBusinessObject()) {
283                throw new RuntimeException("Data Object Class: "
284                        + getDataObjectClass()
285                        + " is not persistable and is not externalizable - configuration error");
286            }
287            // otherwise, let fall through
288        }
289
290        return dataObject;
291    }
292
293    private void forceLoadLazyRelations(AgendaBo agenda) {
294        for (AgendaItemBo item : agenda.getItems()) {
295            for (ActionBo action : item.getRule().getActions()) {
296                if (CollectionUtils.isEmpty(action.getAttributeBos())) { continue; }
297
298                for (ActionAttributeBo actionAttribute : action.getAttributeBos()) {
299                    actionAttribute.getAttributeDefinition();
300                }
301            }
302
303            Tree propTree = item.getRule().refreshPropositionTree(true);
304            walkPropositionTree(item.getRule().getProposition());
305
306            for (RuleAttributeBo ruleAttribute : item.getRule().getAttributeBos()) {
307                ruleAttribute.getAttributeDefinition();
308            }
309        }
310    }
311
312    private void walkPropositionTree(PropositionBo prop) {
313        if (prop == null) { return; }
314
315        if (prop.getParameters() != null) for (PropositionParameterBo param : prop.getParameters()) {
316            param.getPropId();
317        }
318
319        if (prop.getCompoundComponents() != null) for (PropositionBo childProp : prop.getCompoundComponents()) {
320            walkPropositionTree(childProp);
321        }
322    }
323
324    /**
325     * {@inheritDoc}
326     */
327    @Override
328    public void processAfterNew(MaintenanceDocument document, Map<String, String[]> requestParameters) {
329        super.processAfterNew(document, requestParameters);
330        document.getDocumentHeader().setDocumentDescription(NEW_AGENDA_EDITOR_DOCUMENT_TEXT);
331    }
332
333    @Override
334    public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> requestParameters) {
335        super.processAfterEdit(document, requestParameters);
336        document.getDocumentHeader().setDocumentDescription("Modify Agenda Editor Document");
337    }
338
339    @Override
340    public void processAfterCopy(MaintenanceDocument document,
341            Map<String, String[]> parameters) {
342        super.processAfterCopy(document, parameters);
343        AgendaBo agendaBo = ((AgendaEditor) document.getDocumentDataObject()).getAgenda();
344        agendaBo.setVersionNumber(null);
345
346        for (AgendaItemBo agendaItem : agendaBo.getItems()) {
347            agendaItem.setVersionNumber(null);
348        }
349    }
350    @Override
351    public void prepareForSave() {
352        // set agenda attributes
353        AgendaEditor agendaEditor = (AgendaEditor) getDataObject();
354        agendaEditor.getAgenda().setAttributes(agendaEditor.getCustomAttributesMap());
355    }
356
357    @Override
358    public void saveDataObject() {
359        AgendaBo agendaBo = ((AgendaEditor) getDataObject()).getAgenda();
360
361        AgendaItemBo firstItem = null;
362
363        // Find the first agenda item
364        for (AgendaItemBo agendaItem : agendaBo.getItems()) {
365            if (agendaBo.getFirstItemId().equals(agendaItem.getId())) {
366                firstItem = agendaItem;
367            }
368        }
369
370        // if new agenda persist without items.  This works around a chicken and egg problem
371        // with KRMS_AGENDA_T.INIT_AGENDA_ITM_ID and KRMS_AGENDA_ITM_T.AGENDA_ITM_ID both being non-nullable
372        List<AgendaItemBo> agendaItems = agendaBo.getItems();
373        List<AgendaItemBo> updatedItems = new ArrayList<AgendaItemBo>();
374        List<AgendaItemBo> deletedItems = new ArrayList<AgendaItemBo>();
375        AgendaBo existing = null;
376
377        if (agendaBo.getId() != null) {
378            existing = getDataObjectService().find(AgendaBo.class, agendaBo.getId());
379        }
380
381        if (existing == null) {
382            agendaBo.setItems(updatedItems);
383            agendaBo.setFirstItem(null);
384            agendaBo = getDataObjectService().save(agendaBo);
385            getDataObjectService().flush(AgendaBo.class);
386            String agendaBoId = agendaBo.getId();
387            agendaBo = getDataObjectService().find(AgendaBo.class,agendaBoId);
388            agendaBo.setItems(agendaItems);
389            agendaBo.setFirstItem(firstItem);
390        } else {
391            // Create a list of agendaItems that will be used to delete rules when the data object is saved
392            for (AgendaItemBo existingAgendaItem : existing.getItems()) {
393                boolean deletedAgendaItem = true;
394                for (AgendaItemBo agendaItem : agendaBo.getItems()) {
395                    if (agendaItem.getId().equalsIgnoreCase(existingAgendaItem.getId())) {
396                        deletedAgendaItem = false;
397                        break;
398                    }
399                }
400                if (deletedAgendaItem) {
401                    deletedItems.add(existingAgendaItem);
402                }
403            }
404        }
405
406        // handle saving new parameterized terms and processing custom operators
407        for (AgendaItemBo agendaItem : agendaBo.getItems()) {
408            PropositionBo propositionBo = agendaItem.getRule().getProposition();
409            if (propositionBo != null) {
410                saveNewParameterizedTerms(propositionBo);
411                processCustomOperators(propositionBo);
412            }
413        }
414
415        if (agendaBo != null) {
416            flushCacheBeforeSave();
417            getDataObjectService().flush(AgendaBo.class);
418
419            // Need to set the first item for persistence to cascade
420            agendaBo.setFirstItem(firstItem);
421            getDataObjectService().save(agendaBo);
422
423            // delete orphaned propositions, rules, etc.
424            for (String deletedPropId : ((AgendaEditor) getDataObject()).getDeletedPropositionIds()) {
425                PropositionBo toDelete = getDataObjectService().find(PropositionBo.class, deletedPropId);
426
427                getDataObjectService().delete(toDelete);
428            }
429
430            for (AgendaItemBo deletedItem : deletedItems) {
431                if (deletedItem.getRule() != null) {
432                    getDataObjectService().delete(deletedItem.getRule());
433                }
434            }
435        } else {
436            throw new RuntimeException("Cannot save null " + AgendaBo.class.getName() + " with business object service");
437        }
438    }
439
440    private void flushCacheBeforeSave(){
441        //flush krms caches
442        DistributedCacheManagerDecorator distributedCacheManagerDecorator =
443                GlobalResourceLoader.getService(KrmsConstants.KRMS_DISTRIBUTED_CACHE);
444
445        distributedCacheManagerDecorator.getCache(ActionDefinition.Cache.NAME).clear();
446        distributedCacheManagerDecorator.getCache(AgendaItemDefinition.Cache.NAME).clear();
447        distributedCacheManagerDecorator.getCache(AgendaTreeDefinition.Cache.NAME).clear();
448        distributedCacheManagerDecorator.getCache(AgendaDefinition.Cache.NAME).clear();
449        distributedCacheManagerDecorator.getCache(ContextDefinition.Cache.NAME).clear();
450        distributedCacheManagerDecorator.getCache(KrmsAttributeDefinition.Cache.NAME).clear();
451        distributedCacheManagerDecorator.getCache(KrmsTypeDefinition.Cache.NAME).clear();
452        distributedCacheManagerDecorator.getCache(RuleDefinition.Cache.NAME).clear();
453        distributedCacheManagerDecorator.getCache(PropositionDefinition.Cache.NAME).clear();
454        distributedCacheManagerDecorator.getCache(RuleDefinition.Cache.NAME).clear();
455        distributedCacheManagerDecorator.getCache(TermDefinition.Cache.NAME).clear();
456        distributedCacheManagerDecorator.getCache(TermResolverDefinition.Cache.NAME).clear();
457        distributedCacheManagerDecorator.getCache(TermSpecificationDefinition.Cache.NAME).clear();
458    }
459
460    /**
461     * walk the proposition tree and save any new parameterized terms that are contained therein
462     *
463     * @param propositionBo the root proposition from which to search
464     */
465    private void saveNewParameterizedTerms(PropositionBo propositionBo) {
466        if (StringUtils.isBlank(propositionBo.getCompoundOpCode())) {
467            // it is a simple proposition
468            if (!propositionBo.getParameters().isEmpty() && propositionBo.getParameters().get(0).getValue().startsWith(
469                    KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) {
470                String termId = propositionBo.getParameters().get(0).getValue();
471                String termSpecId = termId.substring(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX.length());
472                // create new term
473                TermBo newTerm = new TermBo();
474                newTerm.setDescription(propositionBo.getNewTermDescription());
475                newTerm.setSpecificationId(termSpecId);
476                newTerm.setId(termIdIncrementer.getNewId());
477
478                List<TermParameterBo> params = new ArrayList<TermParameterBo>();
479                for (Map.Entry<String, String> entry : propositionBo.getTermParameters().entrySet()) {
480                    TermParameterBo param = new TermParameterBo();
481                    param.setTerm(newTerm);
482                    param.setName(entry.getKey());
483                    param.setValue(entry.getValue());
484                    param.setId(termParameterIdIncrementer.getNewId());
485
486                    params.add(param);
487                }
488
489                newTerm.setParameters(params);
490
491                getLegacyDataAdapter().linkAndSave(newTerm);
492                propositionBo.getParameters().get(0).setValue(newTerm.getId());
493            }
494        } else {
495            // recurse
496            for (PropositionBo childProp : propositionBo.getCompoundComponents()) {
497                saveNewParameterizedTerms(childProp);
498            }
499        }
500    }
501
502    /**
503     * walk the proposition tree and process any custom operators found, converting them to custom function invocations.
504     *
505     * @param propositionBo the root proposition from which to search and convert
506     */
507    private void processCustomOperators(PropositionBo propositionBo) {
508        if (StringUtils.isBlank(propositionBo.getCompoundOpCode())) {
509            // if it is a simple proposition with a custom operator
510            if (!propositionBo.getParameters().isEmpty() && propositionBo.getParameters().get(2).getValue().startsWith(
511                    KrmsImplConstants.CUSTOM_OPERATOR_PREFIX)) {
512                PropositionParameterBo operatorParam = propositionBo.getParameters().get(2);
513
514                CustomOperator customOperator =
515                        KrmsServiceLocatorInternal.getCustomOperatorUiTranslator().getCustomOperator(operatorParam.getValue());
516
517                FunctionDefinition operatorFunctionDefinition = customOperator.getOperatorFunctionDefinition();
518
519                operatorParam.setParameterType(PropositionParameterType.FUNCTION.getCode());
520                operatorParam.setValue(operatorFunctionDefinition.getId());
521            }
522        } else {
523            // recurse
524            for (PropositionBo childProp : propositionBo.getCompoundComponents()) {
525                processCustomOperators(childProp);
526            }
527        }
528    }
529
530    /**
531     * Build a map from attribute name to attribute definition from all the defined attribute definitions for the
532     * specified agenda type
533     *
534     * @param agendaTypeId
535     * @return
536     */
537    private Map<String, KrmsAttributeDefinition> buildAttributeDefinitionMap(String agendaTypeId) {
538        KrmsAttributeDefinitionService attributeDefinitionService =
539                KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService();
540
541        // build a map from attribute name to definition
542        Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>();
543
544        List<KrmsAttributeDefinition> attributeDefinitions = attributeDefinitionService.findAttributeDefinitionsByType(
545                agendaTypeId);
546
547        for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) {
548            attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition);
549        }
550        return attributeDefinitionMap;
551    }
552
553    @Override
554    public boolean isOldDataObjectInDocument() {
555        boolean isOldDataObjectInExistence = true;
556
557        if (getDataObject() == null) {
558            isOldDataObjectInExistence = false;
559        } else {
560            // dataObject contains a non persistable wrapper - use agenda from the wrapper object instead
561            Map<String, ?> keyFieldValues = getLegacyDataAdapter().getPrimaryKeyFieldValues(
562                    ((AgendaEditor) getDataObject()).getAgenda());
563            for (Object keyValue : keyFieldValues.values()) {
564                if (keyValue == null) {
565                    isOldDataObjectInExistence = false;
566                } else if ((keyValue instanceof String) && StringUtils.isBlank((String) keyValue)) {
567                    isOldDataObjectInExistence = false;
568                }
569
570                if (!isOldDataObjectInExistence) {
571                    break;
572                }
573            }
574        }
575
576        return isOldDataObjectInExistence;
577    }
578
579    // Since the dataObject is a wrapper class we need to return the agendaBo instead.
580    @Override
581    public Class getDataObjectClass() {
582        return AgendaBo.class;
583    }
584
585    @Override
586    public boolean isLockable() {
587        return true;
588    }
589
590    @Override
591    public void processBeforeAddLine(ViewModel model, Object addLine, String collectionId, String collectionPath) {
592        AgendaEditor agendaEditor = getAgendaEditor(model);
593        if (addLine instanceof ActionBo) {
594            ((ActionBo) addLine).setNamespace(agendaEditor.getAgendaItemLine().getRule().getNamespace());
595        }
596
597        super.processBeforeAddLine(model, addLine, collectionId, collectionPath);
598    }
599}