001 /**
002 * Copyright 2005-2013 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 */
016 package org.kuali.rice.krms.impl.ui;
017
018 import org.apache.commons.collections.CollectionUtils;
019 import org.apache.commons.lang.StringUtils;
020 import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
021 import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
022 import org.kuali.rice.core.api.uif.DataType;
023 import org.kuali.rice.core.api.uif.RemotableAttributeField;
024 import org.kuali.rice.core.api.uif.RemotableTextInput;
025 import org.kuali.rice.core.api.util.tree.Node;
026 import org.kuali.rice.core.api.util.tree.Tree;
027 import org.kuali.rice.core.impl.cache.DistributedCacheManagerDecorator;
028 import org.kuali.rice.krad.bo.PersistableBusinessObject;
029 import org.kuali.rice.krad.maintenance.MaintenanceDocument;
030 import org.kuali.rice.krad.maintenance.Maintainable;
031 import org.kuali.rice.krad.maintenance.MaintainableImpl;
032 import org.kuali.rice.krad.service.BusinessObjectService;
033 import org.kuali.rice.krad.service.KRADServiceLocator;
034 import org.kuali.rice.krad.service.SequenceAccessorService;
035 import org.kuali.rice.krad.uif.container.CollectionGroup;
036 import org.kuali.rice.krad.uif.container.Container;
037 import org.kuali.rice.krad.uif.view.View;
038 import org.kuali.rice.krad.util.KRADConstants;
039 import org.kuali.rice.krad.web.form.MaintenanceDocumentForm;
040 import org.kuali.rice.krms.api.KrmsConstants;
041 import org.kuali.rice.krms.api.repository.action.ActionDefinition;
042 import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition;
043 import org.kuali.rice.krms.api.repository.agenda.AgendaItemDefinition;
044 import org.kuali.rice.krms.api.repository.agenda.AgendaTreeDefinition;
045 import org.kuali.rice.krms.api.repository.context.ContextDefinition;
046 import org.kuali.rice.krms.api.repository.proposition.PropositionDefinition;
047 import org.kuali.rice.krms.api.repository.rule.RuleDefinition;
048 import org.kuali.rice.krms.api.repository.term.TermDefinition;
049 import org.kuali.rice.krms.api.repository.term.TermResolverDefinition;
050 import org.kuali.rice.krms.api.repository.term.TermSpecificationDefinition;
051 import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition;
052 import org.kuali.rice.krms.api.repository.type.KrmsTypeDefinition;
053 import org.kuali.rice.krms.impl.repository.ActionBo;
054 import org.kuali.rice.krms.impl.repository.AgendaBo;
055 import org.kuali.rice.krms.impl.repository.AgendaItemBo;
056 import org.kuali.rice.krms.impl.repository.ContextBoService;
057 import org.kuali.rice.krms.impl.repository.KrmsAttributeDefinitionService;
058 import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator;
059 import org.kuali.rice.krms.impl.repository.PropositionBo;
060 import org.kuali.rice.krms.impl.repository.PropositionParameterBo;
061 import org.kuali.rice.krms.impl.repository.RuleBo;
062 import org.kuali.rice.krms.impl.repository.TermBo;
063 import org.kuali.rice.krms.impl.repository.TermParameterBo;
064 import org.kuali.rice.krms.impl.util.KrmsImplConstants;
065 import org.kuali.rice.krms.impl.util.KrmsRetriever;
066
067 import java.util.ArrayList;
068 import java.util.Collections;
069 import java.util.Date;
070 import java.util.HashMap;
071 import java.util.List;
072 import java.util.Map;
073
074 /**
075 * {@link Maintainable} for the {@link AgendaEditor}
076 *
077 * @author Kuali Rice Team (rice.collab@kuali.org)
078 */
079 public class AgendaEditorMaintainable extends MaintainableImpl {
080
081 private static final long serialVersionUID = 1L;
082
083 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
084 AgendaEditorMaintainable.class);
085
086 public static final String NEW_AGENDA_EDITOR_DOCUMENT_TEXT = "New Agenda Editor Document";
087
088 private transient SequenceAccessorService sequenceAccessorService;
089
090 private transient KrmsRetriever krmsRetriever = new KrmsRetriever();
091
092 /**
093 * @return the boService
094 */
095 public BusinessObjectService getBoService() {
096 return KRADServiceLocator.getBusinessObjectService();
097 }
098
099 /**
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 (param.getValue().startsWith(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) {
136 String termSpecId = param.getValue().substring(
137 KrmsImplConstants.PARAMETERIZED_TERM_PREFIX.length());
138 TermResolverDefinition simplestResolver = getSimplestTermResolver(termSpecId,
139 rule.getNamespace());
140
141 // Get the parameters and build RemotableAttributeFields
142 if (simplestResolver != null) {
143 List<String> parameterNames = new ArrayList<String>(simplestResolver.getParameterNames());
144 Collections.sort(parameterNames); // make param order deterministic
145
146 for (String parameterName : parameterNames) {
147 // TODO: also allow for DD parameters if there are matching type attributes
148 RemotableTextInput.Builder controlBuilder = RemotableTextInput.Builder.create();
149 controlBuilder.setSize(64);
150
151 RemotableAttributeField.Builder builder = RemotableAttributeField.Builder.create(
152 parameterName);
153
154 builder.setRequired(true);
155 builder.setDataType(DataType.STRING);
156 builder.setControl(controlBuilder);
157 builder.setLongLabel(parameterName);
158 builder.setShortLabel(parameterName);
159 builder.setMinLength(Integer.valueOf(1));
160 builder.setMaxLength(Integer.valueOf(64));
161
162 results.add(builder.build());
163 }
164 }
165 }
166 }
167 }
168 }
169 return results;
170 }
171
172 /**
173 * finds the term resolver with the fewest parameters that resolves the given term specification
174 *
175 * @param termSpecId the id of the term specification
176 * @param namespace the namespace of the term specification
177 * @return the simples {@link TermResolverDefinition} found, or null if none was found
178 */
179 // package access so that AgendaEditorController can use it too
180 static TermResolverDefinition getSimplestTermResolver(String termSpecId,
181 String namespace) {// Get the term resolver for the term spec
182
183 List<TermResolverDefinition> resolvers =
184 KrmsRepositoryServiceLocator.getTermBoService().findTermResolversByOutputId(termSpecId, namespace);
185
186 TermResolverDefinition simplestResolver = null;
187
188 for (TermResolverDefinition resolver : resolvers) {
189 if (simplestResolver == null || simplestResolver.getParameterNames().size() < resolver.getParameterNames()
190 .size()) {
191 simplestResolver = resolver;
192 }
193 }
194
195 return simplestResolver;
196 }
197
198 /**
199 * Find and return the node containing the proposition that is in currently in edit mode
200 *
201 * @param node the node to start searching from (typically the root)
202 * @return the node that is currently being edited, if any. Otherwise, null.
203 */
204 private Node<RuleTreeNode, String> findEditedProposition(Node<RuleTreeNode, String> node) {
205 Node<RuleTreeNode, String> result = null;
206 if (node.getData() != null && node.getData().getProposition() != null && node.getData().getProposition()
207 .getEditMode()) {
208 result = node;
209 } else {
210 for (Node<RuleTreeNode, String> child : node.getChildren()) {
211 result = findEditedProposition(child);
212 if (result != null) {
213 break;
214 }
215 }
216 }
217 return result;
218 }
219
220 /**
221 * Get the AgendaEditor out of the MaintenanceDocumentForm's newMaintainableObject
222 *
223 * @param model the MaintenanceDocumentForm
224 * @return the AgendaEditor
225 */
226 private AgendaEditor getAgendaEditor(Object model) {
227 MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) model;
228 return (AgendaEditor) maintenanceForm.getDocument().getNewMaintainableObject().getDataObject();
229 }
230
231 public List<RemotableAttributeField> retrieveRuleActionCustomAttributes(View view, Object model,
232 Container container) {
233 AgendaEditor agendaEditor = getAgendaEditor((MaintenanceDocumentForm) model);
234 return krmsRetriever.retrieveRuleActionCustomAttributes(agendaEditor);
235 }
236
237 /**
238 * This only supports a single action within a rule.
239 */
240 public List<RemotableAttributeField> retrieveRuleCustomAttributes(View view, Object model, Container container) {
241 AgendaEditor agendaEditor = getAgendaEditor((MaintenanceDocumentForm) model);
242 return krmsRetriever.retrieveRuleCustomAttributes(agendaEditor);
243 }
244
245 @Override
246 public Object retrieveObjectForEditOrCopy(MaintenanceDocument document, Map<String, String> dataObjectKeys) {
247 Object dataObject = null;
248
249 try {
250 // Since the dataObject is a wrapper class we need to build it and populate with the agenda bo.
251 AgendaEditor agendaEditor = new AgendaEditor();
252 AgendaBo agenda = getLookupService().findObjectBySearch(
253 ((AgendaEditor) getDataObject()).getAgenda().getClass(), dataObjectKeys);
254 if (KRADConstants.MAINTENANCE_COPY_ACTION.equals(getMaintenanceAction())) {
255 String dateTimeStamp = (new Date()).getTime() + "";
256 String newAgendaName = AgendaItemBo.COPY_OF_TEXT + agenda.getName() + " " + dateTimeStamp;
257
258 AgendaBo copiedAgenda = agenda.copyAgenda(newAgendaName, dateTimeStamp);
259
260 document.getDocumentHeader().setDocumentDescription(NEW_AGENDA_EDITOR_DOCUMENT_TEXT);
261 document.setFieldsClearedOnCopy(true);
262 agendaEditor.setAgenda(copiedAgenda);
263 } else {
264 // set custom attributes map in AgendaEditor
265 // agendaEditor.setCustomAttributesMap(agenda.getAttributes());
266 agendaEditor.setAgenda(agenda);
267 }
268 agendaEditor.setCustomAttributesMap(agenda.getAttributes());
269
270 // set extra fields on AgendaEditor
271 agendaEditor.setNamespace(agenda.getContext().getNamespace());
272 agendaEditor.setContextName(agenda.getContext().getName());
273
274 dataObject = agendaEditor;
275 } catch (ClassNotPersistenceCapableException ex) {
276 if (!document.getOldMaintainableObject().isExternalBusinessObject()) {
277 throw new RuntimeException("Data Object Class: "
278 + getDataObjectClass()
279 + " is not persistable and is not externalizable - configuration error");
280 }
281 // otherwise, let fall through
282 }
283
284 return dataObject;
285 }
286
287 /**
288 * Returns the sequenceAssessorService
289 *
290 * @return {@link SequenceAccessorService}
291 */
292 private SequenceAccessorService getSequenceAccessorService() {
293 if (sequenceAccessorService == null) {
294 sequenceAccessorService = KRADServiceLocator.getSequenceAccessorService();
295 }
296 return sequenceAccessorService;
297 }
298
299 /**
300 * {@inheritDoc}
301 */
302 @Override
303 public void processAfterNew(MaintenanceDocument document, Map<String, String[]> requestParameters) {
304 super.processAfterNew(document, requestParameters);
305 document.getDocumentHeader().setDocumentDescription(NEW_AGENDA_EDITOR_DOCUMENT_TEXT);
306 }
307
308 @Override
309 public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> requestParameters) {
310 super.processAfterEdit(document, requestParameters);
311 document.getDocumentHeader().setDocumentDescription("Modify Agenda Editor Document");
312 }
313
314 @Override
315 public void prepareForSave() {
316 // set agenda attributes
317 AgendaEditor agendaEditor = (AgendaEditor) getDataObject();
318 agendaEditor.getAgenda().setAttributes(agendaEditor.getCustomAttributesMap());
319 }
320
321 @Override
322 public void saveDataObject() {
323 AgendaBo agendaBo = ((AgendaEditor) getDataObject()).getAgenda();
324
325 // handle saving new parameterized terms
326 for (AgendaItemBo agendaItem : agendaBo.getItems()) {
327 PropositionBo propositionBo = agendaItem.getRule().getProposition();
328 if (propositionBo != null) {
329 saveNewParameterizedTerms(propositionBo);
330 }
331 }
332
333 if (agendaBo instanceof PersistableBusinessObject) {
334 Map<String, String> primaryKeys = new HashMap<String, String>();
335 primaryKeys.put("id", agendaBo.getId());
336 AgendaBo dbAgendaBo = getBusinessObjectService().findByPrimaryKey(AgendaBo.class, primaryKeys);
337 getBusinessObjectService().delete(dbAgendaBo);
338 flushCacheBeforeSave();
339
340 getBusinessObjectService().linkAndSave(agendaBo);
341 } else {
342 throw new RuntimeException("Cannot save object of type: " + agendaBo + " with business object service");
343 }
344 }
345
346 private void flushCacheBeforeSave(){
347 //flush krms caches
348 DistributedCacheManagerDecorator distributedCacheManagerDecorator =
349 GlobalResourceLoader.getService(KrmsConstants.KRMS_DISTRIBUTED_CACHE);
350
351 distributedCacheManagerDecorator.getCache(ActionDefinition.Cache.NAME).clear();
352 distributedCacheManagerDecorator.getCache(AgendaItemDefinition.Cache.NAME).clear();
353 distributedCacheManagerDecorator.getCache(AgendaTreeDefinition.Cache.NAME).clear();
354 distributedCacheManagerDecorator.getCache(AgendaDefinition.Cache.NAME).clear();
355 distributedCacheManagerDecorator.getCache(ContextDefinition.Cache.NAME).clear();
356 distributedCacheManagerDecorator.getCache(KrmsAttributeDefinition.Cache.NAME).clear();
357 distributedCacheManagerDecorator.getCache(KrmsTypeDefinition.Cache.NAME).clear();
358 distributedCacheManagerDecorator.getCache(RuleDefinition.Cache.NAME).clear();
359 distributedCacheManagerDecorator.getCache(PropositionDefinition.Cache.NAME).clear();
360 distributedCacheManagerDecorator.getCache(RuleDefinition.Cache.NAME).clear();
361 distributedCacheManagerDecorator.getCache(TermDefinition.Cache.NAME).clear();
362 distributedCacheManagerDecorator.getCache(TermResolverDefinition.Cache.NAME).clear();
363 distributedCacheManagerDecorator.getCache(TermSpecificationDefinition.Cache.NAME).clear();
364 }
365
366 /**
367 * walk the proposition tree and save any new parameterized terms that are contained therein
368 *
369 * @param propositionBo the root proposition from which to search
370 */
371 private void saveNewParameterizedTerms(PropositionBo propositionBo) {
372 if (StringUtils.isBlank(propositionBo.getCompoundOpCode())) {
373 // it is a simple proposition
374 if (!propositionBo.getParameters().isEmpty() && propositionBo.getParameters().get(0).getValue().startsWith(
375 KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) {
376 String termId = propositionBo.getParameters().get(0).getValue();
377 String termSpecId = termId.substring(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX.length());
378 // create new term
379 TermBo newTerm = new TermBo();
380 newTerm.setDescription(propositionBo.getNewTermDescription());
381 newTerm.setSpecificationId(termSpecId);
382 newTerm.setId(KRADServiceLocator.getSequenceAccessorService().getNextAvailableSequenceNumber(
383 KrmsMaintenanceConstants.Sequences.TERM_SPECIFICATION, TermBo.class).toString());
384
385 List<TermParameterBo> params = new ArrayList<TermParameterBo>();
386 for (Map.Entry<String, String> entry : propositionBo.getTermParameters().entrySet()) {
387 TermParameterBo param = new TermParameterBo();
388 param.setTermId(newTerm.getId());
389 param.setName(entry.getKey());
390 param.setValue(entry.getValue());
391 param.setId(KRADServiceLocator.getSequenceAccessorService().getNextAvailableSequenceNumber(
392 KrmsMaintenanceConstants.Sequences.TERM_PARAMETER, TermParameterBo.class).toString());
393
394 params.add(param);
395 }
396
397 newTerm.setParameters(params);
398
399 KRADServiceLocator.getBusinessObjectService().linkAndSave(newTerm);
400 propositionBo.getParameters().get(0).setValue(newTerm.getId());
401 }
402 } else {
403 // recurse
404 for (PropositionBo childProp : propositionBo.getCompoundComponents()) {
405 saveNewParameterizedTerms(childProp);
406 }
407 }
408 }
409
410 /**
411 * Build a map from attribute name to attribute definition from all the defined attribute definitions for the
412 * specified agenda type
413 *
414 * @param agendaTypeId
415 * @return
416 */
417 private Map<String, KrmsAttributeDefinition> buildAttributeDefinitionMap(String agendaTypeId) {
418 KrmsAttributeDefinitionService attributeDefinitionService =
419 KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService();
420
421 // build a map from attribute name to definition
422 Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>();
423
424 List<KrmsAttributeDefinition> attributeDefinitions = attributeDefinitionService.findAttributeDefinitionsByType(
425 agendaTypeId);
426
427 for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) {
428 attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition);
429 }
430 return attributeDefinitionMap;
431 }
432
433 @Override
434 public boolean isOldDataObjectInDocument() {
435 boolean isOldDataObjectInExistence = true;
436
437 if (getDataObject() == null) {
438 isOldDataObjectInExistence = false;
439 } else {
440 // dataObject contains a non persistable wrapper - use agenda from the wrapper object instead
441 Map<String, ?> keyFieldValues = getDataObjectMetaDataService().getPrimaryKeyFieldValues(
442 ((AgendaEditor) getDataObject()).getAgenda());
443 for (Object keyValue : keyFieldValues.values()) {
444 if (keyValue == null) {
445 isOldDataObjectInExistence = false;
446 } else if ((keyValue instanceof String) && StringUtils.isBlank((String) keyValue)) {
447 isOldDataObjectInExistence = false;
448 }
449
450 if (!isOldDataObjectInExistence) {
451 break;
452 }
453 }
454 }
455
456 return isOldDataObjectInExistence;
457 }
458
459 // Since the dataObject is a wrapper class we need to return the agendaBo instead.
460 @Override
461 public Class getDataObjectClass() {
462 return AgendaBo.class;
463 }
464
465 @Override
466 public boolean isLockable() {
467 return true;
468 }
469
470 @Override
471 public PersistableBusinessObject getPersistableBusinessObject() {
472 return ((AgendaEditor) getDataObject()).getAgenda();
473 }
474
475 @Override
476 protected void processBeforeAddLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) {
477 AgendaEditor agendaEditor = getAgendaEditor(model);
478 if (addLine instanceof ActionBo) {
479 ((ActionBo) addLine).setNamespace(agendaEditor.getAgendaItemLine().getRule().getNamespace());
480 }
481
482 super.processBeforeAddLine(view, collectionGroup, model, addLine);
483 }
484 }