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