Java tutorial
/** * Copyright 2005-2014 The Kuali Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ecl2.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kuali.rice.krms.impl.ui; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException; import org.kuali.rice.core.api.data.DataType; import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader; import org.kuali.rice.core.api.uif.RemotableAttributeField; import org.kuali.rice.core.api.uif.RemotableTextInput; import org.kuali.rice.core.api.util.tree.Node; import org.kuali.rice.core.api.util.tree.Tree; import org.kuali.rice.core.impl.cache.DistributedCacheManagerDecorator; import org.kuali.rice.krad.maintenance.Maintainable; import org.kuali.rice.krad.maintenance.MaintainableImpl; import org.kuali.rice.krad.maintenance.MaintenanceDocument; import org.kuali.rice.krad.uif.container.CollectionGroup; import org.kuali.rice.krad.uif.container.Container; import org.kuali.rice.krad.uif.view.View; import org.kuali.rice.krad.uif.view.ViewModel; import org.kuali.rice.krad.util.KRADConstants; import org.kuali.rice.krad.util.ObjectUtils; import org.kuali.rice.krad.web.form.MaintenanceDocumentForm; import org.kuali.rice.krms.api.KrmsConstants; import org.kuali.rice.krms.api.repository.action.ActionDefinition; import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition; import org.kuali.rice.krms.api.repository.agenda.AgendaItemDefinition; import org.kuali.rice.krms.api.repository.agenda.AgendaTreeDefinition; import org.kuali.rice.krms.api.repository.context.ContextDefinition; import org.kuali.rice.krms.api.repository.function.FunctionDefinition; import org.kuali.rice.krms.api.repository.operator.CustomOperator; import org.kuali.rice.krms.api.repository.proposition.PropositionDefinition; import org.kuali.rice.krms.api.repository.proposition.PropositionParameterType; import org.kuali.rice.krms.api.repository.rule.RuleDefinition; import org.kuali.rice.krms.api.repository.term.TermDefinition; import org.kuali.rice.krms.api.repository.term.TermResolverDefinition; import org.kuali.rice.krms.api.repository.term.TermSpecificationDefinition; import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition; import org.kuali.rice.krms.api.repository.type.KrmsTypeDefinition; import org.kuali.rice.krms.impl.repository.ActionAttributeBo; import org.kuali.rice.krms.impl.repository.ActionBo; import org.kuali.rice.krms.impl.repository.AgendaBo; import org.kuali.rice.krms.impl.repository.AgendaItemBo; import org.kuali.rice.krms.impl.repository.ContextBoService; import org.kuali.rice.krms.impl.repository.KrmsAttributeDefinitionService; import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator; import org.kuali.rice.krms.impl.repository.PropositionBo; import org.kuali.rice.krms.impl.repository.PropositionParameterBo; import org.kuali.rice.krms.impl.repository.RepositoryBoIncrementer; import org.kuali.rice.krms.impl.repository.RuleAttributeBo; import org.kuali.rice.krms.impl.repository.RuleBo; import org.kuali.rice.krms.impl.repository.TermBo; import org.kuali.rice.krms.impl.repository.TermParameterBo; import org.kuali.rice.krms.impl.util.KrmsImplConstants; import org.kuali.rice.krms.impl.util.KrmsRetriever; import org.kuali.rice.krms.impl.util.KrmsServiceLocatorInternal; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.kuali.rice.krms.impl.repository.BusinessObjectServiceMigrationUtils.findSingleMatching; /** * {@link Maintainable} for the {@link AgendaEditor} * * @author Kuali Rice Team (rice.collab@kuali.org) */ public class AgendaEditorMaintainable extends MaintainableImpl { private static final long serialVersionUID = 1L; private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger .getLogger(AgendaEditorMaintainable.class); public static final String NEW_AGENDA_EDITOR_DOCUMENT_TEXT = "New Agenda Editor Document"; private static final RepositoryBoIncrementer termIdIncrementer = new RepositoryBoIncrementer( TermBo.TERM_SEQ_NAME); private static final RepositoryBoIncrementer termParameterIdIncrementer = new RepositoryBoIncrementer( TermParameterBo.TERM_PARM_SEQ_NAME); private transient KrmsRetriever krmsRetriever = new KrmsRetriever(); /** * return the contextBoService */ private ContextBoService getContextBoService() { return KrmsRepositoryServiceLocator.getContextBoService(); } public List<RemotableAttributeField> retrieveAgendaCustomAttributes(View view, Object model, Container container) { AgendaEditor agendaEditor = getAgendaEditor(model); return krmsRetriever.retrieveAgendaCustomAttributes(agendaEditor); } /** * Retrieve a list of {@link RemotableAttributeField}s for the parameters (if any) required by the resolver for * the selected term in the proposition that is under edit. */ public List<RemotableAttributeField> retrieveTermParameters(View view, Object model, Container container) { List<RemotableAttributeField> results = new ArrayList<RemotableAttributeField>(); AgendaEditor agendaEditor = getAgendaEditor(model); // Figure out which rule is being edited RuleBo rule = agendaEditor.getAgendaItemLine().getRule(); if (null != rule) { // Figure out which proposition is being edited Tree<RuleTreeNode, String> propositionTree = rule.getPropositionTree(); Node<RuleTreeNode, String> editedPropositionNode = findEditedProposition( propositionTree.getRootElement()); if (editedPropositionNode != null) { PropositionBo propositionBo = editedPropositionNode.getData().getProposition(); if (StringUtils.isEmpty(propositionBo.getCompoundOpCode()) && CollectionUtils.size(propositionBo.getParameters()) > 0) { // Get the term ID; if it is a new parameterized term, it will have a special prefix PropositionParameterBo param = propositionBo.getParameters().get(0); if (StringUtils.isNotBlank(param.getValue()) && param.getValue().startsWith(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) { String termSpecId = param.getValue() .substring(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX.length()); TermResolverDefinition simplestResolver = getSimplestTermResolver(termSpecId, rule.getNamespace()); // Get the parameters and build RemotableAttributeFields if (simplestResolver != null) { List<String> parameterNames = new ArrayList<String>( simplestResolver.getParameterNames()); Collections.sort(parameterNames); // make param order deterministic for (String parameterName : parameterNames) { // TODO: also allow for DD parameters if there are matching type attributes RemotableTextInput.Builder controlBuilder = RemotableTextInput.Builder.create(); controlBuilder.setSize(64); RemotableAttributeField.Builder builder = RemotableAttributeField.Builder .create(parameterName); builder.setRequired(true); builder.setDataType(DataType.STRING); builder.setControl(controlBuilder); builder.setLongLabel(parameterName); builder.setShortLabel(parameterName); builder.setMinLength(Integer.valueOf(1)); builder.setMaxLength(Integer.valueOf(64)); results.add(builder.build()); } } } } } } return results; } /** * finds the term resolver with the fewest parameters that resolves the given term specification * * @param termSpecId the id of the term specification * @param namespace the namespace of the term specification * @return the simples {@link TermResolverDefinition} found, or null if none was found */ // package access so that AgendaEditorController can use it too static TermResolverDefinition getSimplestTermResolver(String termSpecId, String namespace) {// Get the term resolver for the term spec List<TermResolverDefinition> resolvers = KrmsRepositoryServiceLocator.getTermBoService() .findTermResolversByOutputId(termSpecId, namespace); TermResolverDefinition simplestResolver = null; for (TermResolverDefinition resolver : resolvers) { if (simplestResolver == null || simplestResolver.getParameterNames().size() < resolver.getParameterNames().size()) { simplestResolver = resolver; } } return simplestResolver; } /** * Find and return the node containing the proposition that is in currently in edit mode * * @param node the node to start searching from (typically the root) * @return the node that is currently being edited, if any. Otherwise, null. */ private Node<RuleTreeNode, String> findEditedProposition(Node<RuleTreeNode, String> node) { Node<RuleTreeNode, String> result = null; if (node.getData() != null && node.getData().getProposition() != null && node.getData().getProposition().getEditMode()) { result = node; } else { for (Node<RuleTreeNode, String> child : node.getChildren()) { result = findEditedProposition(child); if (result != null) { break; } } } return result; } /** * Get the AgendaEditor out of the MaintenanceDocumentForm's newMaintainableObject * * @param model the MaintenanceDocumentForm * @return the AgendaEditor */ private AgendaEditor getAgendaEditor(Object model) { MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) model; return (AgendaEditor) maintenanceForm.getDocument().getNewMaintainableObject().getDataObject(); } public List<RemotableAttributeField> retrieveRuleActionCustomAttributes(View view, Object model, Container container) { AgendaEditor agendaEditor = getAgendaEditor((MaintenanceDocumentForm) model); return krmsRetriever.retrieveRuleActionCustomAttributes(agendaEditor); } /** * This only supports a single action within a rule. */ public List<RemotableAttributeField> retrieveRuleCustomAttributes(View view, Object model, Container container) { AgendaEditor agendaEditor = getAgendaEditor((MaintenanceDocumentForm) model); return krmsRetriever.retrieveRuleCustomAttributes(agendaEditor); } @Override public Object retrieveObjectForEditOrCopy(MaintenanceDocument document, Map<String, String> dataObjectKeys) { Object dataObject = null; try { // Since the dataObject is a wrapper class we need to build it and populate with the agenda bo. AgendaEditor agendaEditor = new AgendaEditor(); AgendaBo agenda = findSingleMatching(getDataObjectService(), ((AgendaEditor) getDataObject()).getAgenda().getClass(), dataObjectKeys); // HACK: force lazy loaded items to be fetched forceLoadLazyRelations(agenda); if (KRADConstants.MAINTENANCE_COPY_ACTION.equals(getMaintenanceAction())) { String dateTimeStamp = (new Date()).getTime() + ""; String newAgendaName = AgendaItemBo.COPY_OF_TEXT + agenda.getName() + " " + dateTimeStamp; AgendaBo copiedAgenda = agenda.copyAgenda(newAgendaName, dateTimeStamp); document.getDocumentHeader().setDocumentDescription(NEW_AGENDA_EDITOR_DOCUMENT_TEXT); document.setFieldsClearedOnCopy(true); agendaEditor.setAgenda(copiedAgenda); } else { // set custom attributes map in AgendaEditor // agendaEditor.setCustomAttributesMap(agenda.getAttributes()); agendaEditor.setAgenda(agenda); } agendaEditor.setCustomAttributesMap(agenda.getAttributes()); // set extra fields on AgendaEditor agendaEditor.setNamespace(agenda.getContext().getNamespace()); agendaEditor.setContextName(agenda.getContext().getName()); dataObject = agendaEditor; } catch (ClassNotPersistenceCapableException ex) { if (!document.getOldMaintainableObject().isExternalBusinessObject()) { throw new RuntimeException("Data Object Class: " + getDataObjectClass() + " is not persistable and is not externalizable - configuration error"); } // otherwise, let fall through } return dataObject; } private void forceLoadLazyRelations(AgendaBo agenda) { for (AgendaItemBo item : agenda.getItems()) { for (ActionBo action : item.getRule().getActions()) { if (CollectionUtils.isEmpty(action.getAttributeBos())) { continue; } for (ActionAttributeBo actionAttribute : action.getAttributeBos()) { actionAttribute.getAttributeDefinition(); } } Tree propTree = item.getRule().refreshPropositionTree(true); walkPropositionTree(item.getRule().getProposition()); for (RuleAttributeBo ruleAttribute : item.getRule().getAttributeBos()) { ruleAttribute.getAttributeDefinition(); } } } private void walkPropositionTree(PropositionBo prop) { if (prop == null) { return; } if (prop.getParameters() != null) for (PropositionParameterBo param : prop.getParameters()) { param.getPropId(); } if (prop.getCompoundComponents() != null) for (PropositionBo childProp : prop.getCompoundComponents()) { walkPropositionTree(childProp); } } /** * {@inheritDoc} */ @Override public void processAfterNew(MaintenanceDocument document, Map<String, String[]> requestParameters) { super.processAfterNew(document, requestParameters); document.getDocumentHeader().setDocumentDescription(NEW_AGENDA_EDITOR_DOCUMENT_TEXT); } @Override public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> requestParameters) { super.processAfterEdit(document, requestParameters); document.getDocumentHeader().setDocumentDescription("Modify Agenda Editor Document"); } @Override public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> parameters) { super.processAfterCopy(document, parameters); AgendaBo agendaBo = ((AgendaEditor) document.getDocumentDataObject()).getAgenda(); agendaBo.setVersionNumber(null); for (AgendaItemBo agendaItem : agendaBo.getItems()) { agendaItem.setVersionNumber(null); } } @Override public void prepareForSave() { // set agenda attributes AgendaEditor agendaEditor = (AgendaEditor) getDataObject(); agendaEditor.getAgenda().setAttributes(agendaEditor.getCustomAttributesMap()); } @Override public void saveDataObject() { AgendaBo agendaBo = ((AgendaEditor) getDataObject()).getAgenda(); // handle saving new parameterized terms and processing custom operators for (AgendaItemBo agendaItem : agendaBo.getItems()) { PropositionBo propositionBo = agendaItem.getRule().getProposition(); if (propositionBo != null) { saveNewParameterizedTerms(propositionBo); processCustomOperators(propositionBo); } } if (agendaBo != null) { flushCacheBeforeSave(); /* AgendaItemBo has columns alwaysId, whenFirstId and whenLastId that have fk constraints to a column (agendaItemid) on the same table. With hibernate in this scenario caching all inserts causes them to be executed at the same time in the DB and we get an integrity violation exception. The solution is to decouple AgendaBo from AgendaItemBo persist the agenaItems one at at time and flush, causing the items to be persisted avoiding the integrity constraint */ if (this.getMaintenanceAction() != null && this.getMaintenanceAction().equals(KRADConstants.MAINTENANCE_COPY_ACTION)) { List<AgendaItemBo> agendaItems = agendaBo.getItems(); agendaBo.setItems(new ArrayList<AgendaItemBo>()); getDataObjectService().save(agendaBo); for (AgendaItemBo item : agendaItems) { getDataObjectService().save(item); getDataObjectService().flush(AgendaItemBo.class); } agendaBo.setItems(agendaItems); } else { getDataObjectService().save(agendaBo); } } else { throw new RuntimeException("Cannot save object of type: " + agendaBo + " with business object service"); } } private void addItemsToListForDeletion(List<AgendaItemBo> deletionOrder, AgendaItemBo agendaItemBo) { if (!deletionOrder.contains(agendaItemBo) && ObjectUtils.isNotNull(agendaItemBo)) { deletionOrder.add(agendaItemBo); } if (ObjectUtils.isNotNull(agendaItemBo)) { if (StringUtils.isNotBlank(agendaItemBo.getWhenTrueId()) && !deletionOrder.contains(agendaItemBo.getWhenTrue())) { deletionOrder.add(agendaItemBo.getWhenTrue()); addItemsToListForDeletion(deletionOrder, agendaItemBo.getWhenTrue()); } if (StringUtils.isNotBlank(agendaItemBo.getWhenFalseId()) && !deletionOrder.contains(agendaItemBo.getWhenFalse())) { deletionOrder.add(agendaItemBo.getWhenFalse()); addItemsToListForDeletion(deletionOrder, agendaItemBo.getWhenFalse()); } if (StringUtils.isNotBlank(agendaItemBo.getAlwaysId()) && !deletionOrder.contains(agendaItemBo.getAlways())) { deletionOrder.add(agendaItemBo.getAlways()); addItemsToListForDeletion(deletionOrder, agendaItemBo.getAlways()); } } } private void flushCacheBeforeSave() { //flush krms caches DistributedCacheManagerDecorator distributedCacheManagerDecorator = GlobalResourceLoader .getService(KrmsConstants.KRMS_DISTRIBUTED_CACHE); distributedCacheManagerDecorator.getCache(ActionDefinition.Cache.NAME).clear(); distributedCacheManagerDecorator.getCache(AgendaItemDefinition.Cache.NAME).clear(); distributedCacheManagerDecorator.getCache(AgendaTreeDefinition.Cache.NAME).clear(); distributedCacheManagerDecorator.getCache(AgendaDefinition.Cache.NAME).clear(); distributedCacheManagerDecorator.getCache(ContextDefinition.Cache.NAME).clear(); distributedCacheManagerDecorator.getCache(KrmsAttributeDefinition.Cache.NAME).clear(); distributedCacheManagerDecorator.getCache(KrmsTypeDefinition.Cache.NAME).clear(); distributedCacheManagerDecorator.getCache(RuleDefinition.Cache.NAME).clear(); distributedCacheManagerDecorator.getCache(PropositionDefinition.Cache.NAME).clear(); distributedCacheManagerDecorator.getCache(RuleDefinition.Cache.NAME).clear(); distributedCacheManagerDecorator.getCache(TermDefinition.Cache.NAME).clear(); distributedCacheManagerDecorator.getCache(TermResolverDefinition.Cache.NAME).clear(); distributedCacheManagerDecorator.getCache(TermSpecificationDefinition.Cache.NAME).clear(); } /** * walk the proposition tree and save any new parameterized terms that are contained therein * * @param propositionBo the root proposition from which to search */ private void saveNewParameterizedTerms(PropositionBo propositionBo) { if (StringUtils.isBlank(propositionBo.getCompoundOpCode())) { // it is a simple proposition if (!propositionBo.getParameters().isEmpty() && propositionBo.getParameters().get(0).getValue() .startsWith(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) { String termId = propositionBo.getParameters().get(0).getValue(); String termSpecId = termId.substring(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX.length()); // create new term TermBo newTerm = new TermBo(); newTerm.setDescription(propositionBo.getNewTermDescription()); newTerm.setSpecificationId(termSpecId); newTerm.setId(termIdIncrementer.getNewId()); List<TermParameterBo> params = new ArrayList<TermParameterBo>(); for (Map.Entry<String, String> entry : propositionBo.getTermParameters().entrySet()) { TermParameterBo param = new TermParameterBo(); param.setTerm(newTerm); param.setName(entry.getKey()); param.setValue(entry.getValue()); param.setId(termParameterIdIncrementer.getNewId()); params.add(param); } newTerm.setParameters(params); getLegacyDataAdapter().linkAndSave(newTerm); propositionBo.getParameters().get(0).setValue(newTerm.getId()); } } else { // recurse for (PropositionBo childProp : propositionBo.getCompoundComponents()) { saveNewParameterizedTerms(childProp); } } } /** * walk the proposition tree and process any custom operators found, converting them to custom function invocations. * * @param propositionBo the root proposition from which to search and convert */ private void processCustomOperators(PropositionBo propositionBo) { if (StringUtils.isBlank(propositionBo.getCompoundOpCode())) { // if it is a simple proposition with a custom operator if (!propositionBo.getParameters().isEmpty() && propositionBo.getParameters().get(2).getValue() .startsWith(KrmsImplConstants.CUSTOM_OPERATOR_PREFIX)) { PropositionParameterBo operatorParam = propositionBo.getParameters().get(2); CustomOperator customOperator = KrmsServiceLocatorInternal.getCustomOperatorUiTranslator() .getCustomOperator(operatorParam.getValue()); FunctionDefinition operatorFunctionDefinition = customOperator.getOperatorFunctionDefinition(); operatorParam.setParameterType(PropositionParameterType.FUNCTION.getCode()); operatorParam.setValue(operatorFunctionDefinition.getId()); } } else { // recurse for (PropositionBo childProp : propositionBo.getCompoundComponents()) { processCustomOperators(childProp); } } } /** * Build a map from attribute name to attribute definition from all the defined attribute definitions for the * specified agenda type * * @param agendaTypeId * @return */ private Map<String, KrmsAttributeDefinition> buildAttributeDefinitionMap(String agendaTypeId) { KrmsAttributeDefinitionService attributeDefinitionService = KrmsRepositoryServiceLocator .getKrmsAttributeDefinitionService(); // build a map from attribute name to definition Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>(); List<KrmsAttributeDefinition> attributeDefinitions = attributeDefinitionService .findAttributeDefinitionsByType(agendaTypeId); for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) { attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition); } return attributeDefinitionMap; } @Override public boolean isOldDataObjectInDocument() { boolean isOldDataObjectInExistence = true; if (getDataObject() == null) { isOldDataObjectInExistence = false; } else { // dataObject contains a non persistable wrapper - use agenda from the wrapper object instead Map<String, ?> keyFieldValues = getLegacyDataAdapter() .getPrimaryKeyFieldValues(((AgendaEditor) getDataObject()).getAgenda()); for (Object keyValue : keyFieldValues.values()) { if (keyValue == null) { isOldDataObjectInExistence = false; } else if ((keyValue instanceof String) && StringUtils.isBlank((String) keyValue)) { isOldDataObjectInExistence = false; } if (!isOldDataObjectInExistence) { break; } } } return isOldDataObjectInExistence; } // Since the dataObject is a wrapper class we need to return the agendaBo instead. @Override public Class getDataObjectClass() { return AgendaBo.class; } @Override public boolean isLockable() { return true; } @Override public void processBeforeAddLine(ViewModel model, Object addLine, String collectionId, String collectionPath) { AgendaEditor agendaEditor = getAgendaEditor(model); if (addLine instanceof ActionBo) { ((ActionBo) addLine).setNamespace(agendaEditor.getAgendaItemLine().getRule().getNamespace()); } super.processBeforeAddLine(model, addLine, collectionId, collectionPath); } }