/** * Copyright (C) 2012 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0 * * 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.jbpm.formModeler.core.processing.impl; import org.apache.commons.jxpath.JXPathContext; import org.jbpm.formModeler.core.processing.fieldHandlers.subform.utils.SubFormHelper; import org.jbpm.formModeler.core.processing.formProcessing.*; import org.slf4j.Logger; import org.jbpm.formModeler.api.model.DataHolder; import org.jbpm.formModeler.core.FieldHandlersManager; import org.jbpm.formModeler.core.config.RangeProviderManager; import org.jbpm.formModeler.core.processing.*; import org.jbpm.formModeler.core.processing.fieldHandlers.NumericFieldHandler; import org.jbpm.formModeler.core.processing.formStatus.FormStatus; import org.jbpm.formModeler.core.processing.formStatus.FormStatusManager; import org.jbpm.formModeler.api.model.Field; import org.jbpm.formModeler.api.model.Form; import org.apache.commons.lang3.StringUtils; import org.jbpm.formModeler.api.client.FormRenderContext; import org.jbpm.formModeler.api.client.FormRenderContextManager; import org.jbpm.formModeler.core.util.BindingExpressionUtil; import org.slf4j.LoggerFactory; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import java.io.Serializable; import java.util.*; @ApplicationScoped public class FormProcessorImpl implements FormProcessor, Serializable { private Logger log = LoggerFactory.getLogger(FormProcessor.class); // TODO: fix formulas @Inject private FormulasCalculatorChangeProcessor formChangeProcessor; @Inject private RangeProviderManager rangeProviderManager; @Inject private DefaultFormulaProcessor defaultFormulaProcessor; @Inject private FieldHandlersManager fieldHandlersManager; @Inject private FormRenderContextManager formRenderContextManager; @Inject private NamespaceManager namespaceManager; @Inject private SubFormHelper helper; private BindingExpressionUtil bindingExpressionUtil = BindingExpressionUtil.getInstance(); protected FormStatus getContextFormStatus(FormRenderContext context) { return FormStatusManager.lookup().getFormStatus(context.getForm(), context.getUID()); } protected FormStatus getFormStatus(Form form, String namespace) { return getFormStatus(form, namespace, new HashMap(), new HashMap<String, Object>()); } protected FormStatus getFormStatus(Form form, String namespace, Map<String, Object> currentValues, Map<String, Object> loadedObjects) { FormStatus formStatus = FormStatusManager.lookup().getFormStatus(form, namespace); return formStatus != null ? formStatus : createFormStatus(form, namespace, currentValues, loadedObjects); } protected boolean existsFormStatus(Form form, String namespace) { FormStatus formStatus = FormStatusManager.lookup().getFormStatus(form, namespace); return formStatus != null; } protected FormStatus createFormStatus(Form form, String namespace, Map currentValues, Map<String, Object> loadedObjects) { FormStatus fStatus = FormStatusManager.lookup().createFormStatus(form, namespace, currentValues); fStatus.setLoadedObjects(loadedObjects); setDefaultValues(form, namespace, currentValues); formChangeProcessor.process(form, namespace, new FormChangeResponse()); return fStatus; } protected void setDefaultValues(Form form, String namespace, Map currentValues) { if (form != null) { Set formFields = form.getFormFields(); Map rangeFormulas = (Map) getAttribute(form, namespace, FormStatusData.CALCULATED_RANGE_FORMULAS); if (rangeFormulas == null) { rangeFormulas = new HashMap(); setAttribute(form, namespace, FormStatusData.CALCULATED_RANGE_FORMULAS, rangeFormulas); } for (Iterator iterator = formFields.iterator(); iterator.hasNext();) { Field field = (Field) iterator.next(); String inputName = getPrefix(form, namespace) + field.getFieldName(); try { // Init ranges for simple combos String rangeFormula = field.getRangeFormula(); if (rangeFormula!=null && rangeFormula.trim().length() > 0) { rangeFormulas.put(field.getFieldName(), rangeProviderManager.getRangeValues(rangeFormula, namespace)); } } catch (Exception e) { log.error("Error obtaining default values for " + inputName, e); } } defaultFormulaProcessor.process(FormProcessingContext.defaultFormulaProcessingContext(form, namespace), null); } } protected void destroyFormStatus(Form form, String namespace) { FormStatusManager.lookup().destroyFormStatus(form.getId(), namespace); } public void setValues(Form form, String namespace, Map parameterMap, Map filesMap) { setValues(form, namespace, parameterMap, filesMap, false); } public void setValues(Form form, String namespace, Map parameterMap, Map filesMap, boolean incremental) { if (form != null) { namespace = StringUtils.defaultIfEmpty(namespace, DEFAULT_NAMESPACE); FormStatus formStatus = getFormStatus(form, namespace); if (incremental) { Map mergedParameterMap = new HashMap(); if (formStatus.getLastParameterMap() != null) mergedParameterMap.putAll(formStatus.getLastParameterMap()); if (parameterMap != null) mergedParameterMap.putAll(parameterMap); formStatus.setLastParameterMap(mergedParameterMap); } else { formStatus.setLastParameterMap(parameterMap); } String inputsPrefix = getPrefix(form, namespace); for (Field field : form.getFormFields()) { setFieldValue(field, formStatus, inputsPrefix, parameterMap, filesMap, incremental); } } } public void modify(Form form, String namespace, String fieldName, Object value) { FormStatus formStatus = getFormStatus(form, namespace); formStatus.getInputValues().put(fieldName, value); propagateChangesToParentFormStatuses(formStatus, fieldName, value); } public void setAttribute(Form form, String namespace, String attributeName, Object attributeValue) { if (form != null) { FormStatus formStatus = getFormStatus(form, namespace); formStatus.getAttributes().put(attributeName, attributeValue); } } public Object getAttribute(Form form, String namespace, String attributeName) { if (form != null){ FormStatus formStatus = getFormStatus(form, namespace); return formStatus.getAttributes().get(attributeName); } return null; } @Override public void setFieldValue(Field field, String namespace, Map parametersMap, Map filesMap, boolean incremental) { FormStatus formStatus = getFormStatus(field.getForm(), namespace); setFieldValue(field, formStatus, getPrefix(field.getForm(), namespace), parametersMap, filesMap, incremental); } protected void setFieldValue(Field field, FormStatus formStatus, String inputsPrefix, Map parameterMap, Map filesMap, boolean incremental) { String fieldName = field.getFieldName(); String inputName = inputsPrefix + fieldName; FieldHandler handler = fieldHandlersManager.getHandler(field.getFieldType()); try { Object previousValue = formStatus.getInputValues().get(fieldName); boolean isRequired = field.getFieldRequired().booleanValue(); Object value = null; boolean emptyNumber = false; try { value = handler.getValue(field, inputName, parameterMap, filesMap, field.getFieldType().getFieldClass(), previousValue); } catch (NumericFieldHandler.EmptyNumberException ene) { //Treat this case in particular, as returning null in a numeric field would make related formulas not working. emptyNumber = true; } if (incremental && value == null && !emptyNumber) { if (log.isDebugEnabled()) log.debug("Refusing to overwrite input value for parameter " + fieldName); } else { formStatus.getInputValues().put(fieldName, value); try { propagateChangesToParentFormStatuses(formStatus, fieldName, value); } catch (Exception e) { log.debug("Can't propagate changes to parentFormStatuses: ", e); } boolean isEmpty = handler.isEmpty(value); if (isRequired && isEmpty && !incremental) { log.debug("Missing required field " + fieldName); formStatus.getWrongFields().add(fieldName); } else { formStatus.removeWrongField(fieldName); } } } catch (ProcessingMessagedException pme) { log.debug("Processing field: ", pme); formStatus.addErrorMessages(fieldName, pme.getMessages()); } catch (Exception e) { log.debug("Error setting field value:", e); if (!incremental) { //formStatus.getInputValues().put(fieldName, null); formStatus.getWrongFields().add(fieldName); } } } protected void propagateChangesToParentFormStatuses(FormStatus formStatus, String fieldName, Object value) { FormStatus parent = FormStatusManager.lookup().getParent(formStatus); if (parent != null) { String fieldNameInParent = NamespaceManager.lookup().getNamespace(formStatus.getNamespace()).getFieldNameInParent(); Object valueInParent = parent.getInputValues().get(fieldNameInParent); if (valueInParent != null) { Map parentMapObjectRepresentation = null; if (valueInParent instanceof Map) { parentMapObjectRepresentation = (Map) valueInParent; } else if (valueInParent instanceof Map[]) { //Take the correct value Integer pos = helper.getEditFieldPosition( formStatus.getNamespace() ); if (pos != null) { parentMapObjectRepresentation = ((Map[]) valueInParent)[pos.intValue()]; } } if (parentMapObjectRepresentation != null) { //Copy my value to parent parentMapObjectRepresentation.put(fieldName, value); propagateChangesToParentFormStatuses(parent, fieldNameInParent, valueInParent); } } } } public FormStatusData read(String ctxUid) { FormStatusDataImpl data = null; try { FormRenderContext context = formRenderContextManager.getFormRenderContext(ctxUid); if (context == null ) return null; FormStatus formStatus = getContextFormStatus(context); boolean isNew = formStatus == null; if (isNew) { formStatus = createContextFormStatus(context); } data = new FormStatusDataImpl(formStatus, isNew); } catch (Exception e) { log.error("Error: ", e); } return data; } protected FormStatus createContextFormStatus(FormRenderContext context) throws Exception { Map<String, Object> loadedObjects = new HashMap<String, Object>(); Map values = readValuesToLoad(context.getForm(), context.getInputData(), context.getOutputData(), loadedObjects, context.getUID()); return getFormStatus(context.getForm(), context.getUID(), values, loadedObjects); } @Override public Map readValuesToLoad(Form form, Map inputData, Map outputData, Map loadedObjects, String namespace) { Map values = new HashMap(); Set<Field> fields = form.getFormFields(); if (fields != null) { for (Field field : form.getFormFields()) { String inputExperession = field.getInputBinding(); String outputExpression = field.getOutputBinding(); boolean hasInput = !StringUtils.isEmpty(inputExperession); boolean hasOutput = !StringUtils.isEmpty(outputExpression); if (!hasInput && !hasOutput) continue; boolean readFromInput = (hasInput && !hasOutput) || (hasInput && outputData.isEmpty()); DataHolder dataHolder = form.getDataHolderByField(field); Object value; if (dataHolder == null) { if (readFromInput) value = getUnbindedFieldValue(field.getInputBinding(), inputData); else value = getUnbindedFieldValue(field.getOutputBinding(), outputData); } else { Object loadedObject = loadedObjects.get(dataHolder.getUniqeId()); if (loadedObject == null) { if (outputData.isEmpty()) loadedObject = inputData.get(dataHolder.getInputId()); else loadedObject = outputData.get(dataHolder.getOuputId()); loadedObjects.put(dataHolder.getUniqeId(), loadedObject); } if (readFromInput) value = getBindedValue(field, dataHolder, inputExperession, inputData, loadedObjects, namespace); else value = getBindedValue(field, dataHolder, outputExpression, outputData, loadedObjects, namespace); } values.put(field.getFieldName(), value); } } return values; } protected Object getUnbindedFieldValue(String bindingExpression, Map<String, Object> bindingData) { if (bindingExpression.indexOf("/") != -1) { try { String root = bindingExpression.substring(0, bindingExpression.indexOf("/")); String expression = bindingExpression.substring(root.length() + 1); Object object = bindingData.get(root); JXPathContext ctx = JXPathContext.newContext(object); return ctx.getValue(expression); } catch (Exception e) { log.warn("Error getting value for xpath xpression '{}': {}", bindingExpression, e); } } return bindingData.get(bindingExpression); } protected Object getBindedValue(Field field, DataHolder holder, String bindingExpression, Map<String, Object> bindingData, Map loadedObjects, String namespace) { Object value = null; try { Object bindingValue = loadedObjects.get(holder.getUniqeId()); if (bindingValue != null && holder.isAssignableValue(bindingValue)) { if (!loadedObjects.containsKey(holder.getUniqeId())) loadedObjects.put(holder.getUniqeId(), bindingValue); value = holder.readFromBindingExperssion(bindingValue, bindingExpression); } } catch (Exception e) { log.warn("Unable to read value from expression '" + bindingExpression + "'. Error: ", e); value = bindingData.get(bindingExpression); } FieldHandler handler = fieldHandlersManager.getHandler(field.getFieldType()); if (handler instanceof PersistentFieldHandler) { String inputName = getPrefix(field.getForm(), namespace) + field.getFieldName(); value = ((PersistentFieldHandler) handler).getStatusValue(field, inputName, value, loadedObjects); } return value; } public FormStatusData read(Form form, String namespace, Map formValues) { return read(form, namespace, formValues, new HashMap()); } @Override public FormStatusData read(Form form, String namespace, Map<String, Object> formValues, Map<String, Object> loadedObjects) { boolean exists = existsFormStatus(form, namespace); if (formValues == null) formValues = new HashMap(); FormStatus formStatus = getFormStatus(form, namespace, formValues, loadedObjects); FormStatusDataImpl data = null; try { data = new FormStatusDataImpl(formStatus, !exists); } catch (Exception e) { log.error("Error: ", e); } return data; } public FormStatusData read(Form form, String namespace) { return read(form, namespace, new HashMap<String, Object>()); } public void persist(String ctxUid) throws Exception { ctxUid = StringUtils.defaultIfEmpty(ctxUid, FormProcessor.DEFAULT_NAMESPACE); persist(formRenderContextManager.getFormRenderContext(ctxUid)); } public void persist(FormRenderContext context) throws Exception { Form form = context.getForm(); Map mapToPersist = getFilteredMapRepresentationToPersist(form, context.getUID()); Map<String, Object> result = context.getOutputData(); for (Iterator it = mapToPersist.keySet().iterator(); it.hasNext();) { String fieldName = (String) it.next(); Field field = form.getField(fieldName); if (field != null) { DataHolder holder = form.getDataHolderByField(field); String bindingString = field.getOutputBinding(); if (StringUtils.isEmpty(bindingString)) continue; Object value = persistField(field, mapToPersist, holder, context.getUID()); bindingString = bindingExpressionUtil.extractBindingExpression(bindingString); boolean simpleBinding = StringUtils.isEmpty(bindingString) || bindingString.indexOf("/") == -1; if (holder == null || simpleBinding) result.put(bindingString, value); else { String holderFieldId = bindingString.substring((holder.getOuputId() + "/").length()); Object holderOutputValue = result.get(holder.getOuputId()); if (holderOutputValue == null || !holder.isAssignableValue(holderOutputValue)) { holderOutputValue = context.getInputData().get(holder.getInputId()); if (holderOutputValue == null || !holder.isAssignableValue(holderOutputValue)) holderOutputValue = holder.createInstance(context); result.put(holder.getOuputId(), holderOutputValue); } holder.writeValue(holderOutputValue, holderFieldId, value); } } } } @Override public Object persistFormHolder(Form form, String namespace, Map<String, Object> mapToPersist, DataHolder holder, Object loadedObject) throws Exception { if (holder == null) return null; if (loadedObject == null) { FormRenderContext context = formRenderContextManager.getRootContext(namespace); loadedObject = holder.createInstance(context); } for (Iterator it = mapToPersist.keySet().iterator(); it.hasNext();) { String fieldName = (String) it.next(); Field field = form.getField(fieldName); if (field != null && holder.isAssignableForField(field)) { String bindingString = field.getOutputBinding(); if (StringUtils.isEmpty(bindingString)) continue; bindingString = bindingExpressionUtil.extractBindingExpression(bindingString); String holderFieldId = bindingString.substring(holder.getOuputId().length() + 1); Object value = persistField(field, mapToPersist, holder, namespace); holder.writeValue(loadedObject, holderFieldId, value); } } return loadedObject; } protected Object persistField(Field field, Map<String, Object> mapToPersist, DataHolder holder, String namespace) throws Exception{ String bindingString = field.getOutputBinding(); if (holder == null && !StringUtils.isEmpty(bindingString)) return mapToPersist.get(field.getFieldName()); bindingString = bindingExpressionUtil.extractBindingExpression(bindingString); boolean complexBinding = bindingString.indexOf("/") > 0; if (complexBinding) { String holderId = bindingString.substring(0, bindingString.indexOf("/")); String holderFieldId = bindingString.substring(holderId.length() + 1); if (holder != null && !StringUtils.isEmpty(holderFieldId)) { FieldHandler handler = fieldHandlersManager.getHandler(field.getFieldType()); if (handler instanceof PersistentFieldHandler) { String inputName = getPrefix(field.getForm(), namespace) + field.getFieldName(); return ((PersistentFieldHandler) handler).persist(field, inputName, mapToPersist.get( field.getFieldName() )); } else return mapToPersist.get(field.getFieldName()); } } return mapToPersist.get(field.getFieldName()); } public Map getMapRepresentationToPersist(Form form, String namespace) throws Exception { namespace = StringUtils.defaultIfEmpty(namespace, DEFAULT_NAMESPACE); Map m = new HashMap(); FormStatus formStatus = getFormStatus(form, namespace); if (!formStatus.getWrongFields().isEmpty()) { throw new IllegalArgumentException("Validation error."); } fillObjectValues(m, formStatus.getInputValues(), form); Set s = (Set) m.get(MODIFIED_FIELD_NAMES); if (s == null) { m.put(MODIFIED_FIELD_NAMES, s = new TreeSet()); } s.addAll(form.getFieldNames()); return m; } protected Map getFilteredMapRepresentationToPersist(Form form, String namespace) throws Exception { Map inputValues = getMapRepresentationToPersist(form, namespace); Map mapToPersist = filterMapRepresentationToPersist(inputValues); return mapToPersist; } public Map filterMapRepresentationToPersist(Map inputValues) throws Exception { Map filteredMap = new HashMap(); Set keys = inputValues.keySet(); for (Iterator iterator = keys.iterator(); iterator.hasNext();) { String key = (String) iterator.next(); filteredMap.put(key, inputValues.get(key)); } return filteredMap; } /** * Copy to obj values read from status map values * * @param obj * @param values * @throws Exception */ protected void fillObjectValues(final Map obj, Map values, Form form) throws Exception { Map valuesToSet = new HashMap(); for (Iterator it = values.keySet().iterator(); it.hasNext();) { String propertyName = (String) it.next(); Object propertyValue = values.get(propertyName); valuesToSet.put(propertyName, propertyValue); } obj.putAll(valuesToSet); } @Override public void clear(FormRenderContext context) { clear(context.getForm(), context.getUID()); } @Override public void clear(String ctxUID) { clear(formRenderContextManager.getFormRenderContext(ctxUID)); } public void clear(Form form, String namespace) { if (log.isDebugEnabled()) log.debug("Clearing form status for form " + form.getName() + " with namespace '" + namespace + "'"); destroyFormStatus(form, namespace); } public void clearField(Form form, String namespace, String fieldName) { FormStatus formStatus = getFormStatus(form, namespace); formStatus.getInputValues().remove(fieldName); } public void clearFieldErrors(Form form, String namespace) { FormStatusManager.lookup().cascadeClearWrongFields(form.getId(), namespace); } public void forceWrongField(Form form, String namespace, String fieldName) { FormStatusManager.lookup().getFormStatus(form, namespace).getWrongFields().add(fieldName); } protected String getPrefix(Form form, String namespace) { return namespace + FormProcessor.NAMESPACE_SEPARATOR + form.getId() + FormProcessor.NAMESPACE_SEPARATOR; } }