/* * * Copyright 2005 AgileTec s.r.l. (http://www.agiletec.it) All rights reserved. * * This file is part of jAPS software. * jAPS is a free software; * you can redistribute it and/or modify it * under the terms of the GNU General Public License (GPL) as published by the Free Software Foundation; version 2. * * See the file License for the specific language governing permissions * and limitations under the License * * * * Copyright 2005 AgileTec s.r.l. (http://www.agiletec.it) All rights reserved. * */ package com.agiletec.apsadmin.system.entity.attribute.manager; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import ognl.Ognl; import ognl.OgnlContext; import ognl.OgnlException; import com.agiletec.aps.system.ApsSystemUtils; import com.agiletec.aps.system.common.entity.model.IApsEntity; import com.agiletec.aps.system.common.entity.model.attribute.AttributeInterface; import com.agiletec.aps.system.common.entity.model.attribute.util.OgnlValidationRule; import com.agiletec.aps.system.exception.ApsSystemException; import com.agiletec.aps.system.services.i18n.II18nManager; import com.agiletec.aps.system.services.lang.ILangManager; import com.agiletec.aps.system.services.lang.Lang; import com.agiletec.apsadmin.system.BaseAction; import com.agiletec.apsadmin.system.entity.attribute.AttributeTracer; import com.opensymphony.xwork2.ActionSupport; /** * This abstract class is the base for the managers of all attributes. * For the 'complex' attributes this class must be directly extended, otherwise * -for 'simple' attributes- this class is extended by the managers of the * 'mono-language' and 'multi-language' Attributes. * @author E.Santoboni */ public abstract class AbstractAttributeManager implements AttributeManagerInterface { @Override public void checkEntityAttribute(ActionSupport action, Map<String, AttributeManagerInterface> attributeManagers, AttributeInterface attribute, IApsEntity entity) { this.setAttributeManagers(attributeManagers); this.checkAttribute(action, attribute, new AttributeTracer(), entity); } /** * Basic method for attribute checking. * This method knows in advance all the possible combinations of attributes in the system; * if new combinations are needed is it necessary to modify this method and * define the coherency checks and the control logic. * This method must be modified, together with the 'tracer' class, if new 'complex' * attributes are added so to properly manages the messages related to the new combinations available. * With 'complex' attributes this method must be always extended. * * @param action The action to fill with the appropriate errors, if any * @param attribute The current attribute, both 'simple' or 'complex', to check. * @param tracer The 'tracer' class needed to find the position of the attribute inside a 'composite' one. * @param entity The entity to check. */ protected void checkAttribute(ActionSupport action, AttributeInterface attribute, AttributeTracer tracer, IApsEntity entity) { if (tracer.isMonoListElement()) { if (tracer.isCompositeElement()) { this.checkMonoListCompositeElement(action, attribute, tracer, entity); } else { this.checkMonoListElement(action, attribute, tracer, entity); } } else if (tracer.isListElement()) { this.checkListElement(action, attribute, tracer, entity); } else { this.checkSingleAttribute(action, attribute, tracer, entity); } this.checkExpression(action, attribute, tracer, entity); } protected void checkExpression(ActionSupport action, AttributeInterface attribute, AttributeTracer tracer, IApsEntity entity) { OgnlValidationRule ognlValidationRule = attribute.getValidationRules().getOgnlValidationRule(); if (null == ognlValidationRule) return; String expression = ognlValidationRule.getExpression(); if (null == expression || expression.trim().length() == 0) return; if (ognlValidationRule.isEvalExpressionOnValuedAttribute() && this.getState(attribute, tracer) == EMPTY_ATTRIBUTE_STATE) return; try { Object expr = Ognl.parseExpression(expression); OgnlContext ctx = this.createContextForExpressionValidation(attribute, tracer, entity); Boolean value = (Boolean) Ognl.getValue(expr, ctx, attribute, Boolean.class); if (!value) { String messageAttributePositionPrefix = this.createErrorMessageAttributePositionPrefix(action, attribute, tracer); String formFieldName = tracer.getFormFieldName(attribute); String ognlMessage = this.getOgnlExpressionMessage(ognlValidationRule, action); action.addFieldError(formFieldName, messageAttributePositionPrefix + " " + ognlMessage); } } catch (OgnlException e) { ApsSystemUtils.logThrowable(e, this, "checkExpression", "Error on evaluation of expression : " + expression); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "checkExpression"); throw new RuntimeException("Generic Error on evaluation Ognl Expression", t); } } protected OgnlContext createContextForExpressionValidation(AttributeInterface attribute, AttributeTracer tracer, IApsEntity entity) { OgnlContext context = new OgnlContext(); if (null != this.getLangManager()) { Map<String, Lang> langs = new HashMap<String, Lang>(); List<Lang> langList = this.getLangManager().getLangs(); for (int i = 0; i < langList.size(); i++) { Lang lang = langList.get(i); langs.put(lang.getCode(), lang); } context.put("langs", langs); } context.put("attribute", attribute); context.put("entity", attribute.getParentEntity()); if (tracer.isCompositeElement()) { context.put("parent", tracer.getParentAttribute()); } else { if (tracer.isListElement() || tracer.isMonoListElement()) { context.put("parent", entity.getAttribute(attribute.getName())); context.put("index", tracer.getListIndex()); } if (tracer.isListElement()) { context.put("listLang", tracer.getListLang()); } } return context; } protected String getOgnlExpressionMessage(OgnlValidationRule ognlValidationRule, ActionSupport action) throws ApsSystemException { String ognlMessage = ognlValidationRule.getErrorMessage(); if (null != ognlMessage && ognlMessage.trim().length() > 0) { return ognlMessage; } try { String labelKey = ognlValidationRule.getErrorMessageKey(); Lang currentLang = this.getLangManager().getDefaultLang(); if (action instanceof BaseAction) { currentLang = ((BaseAction) action).getCurrentLang(); } String label = this.getI18nManager().getLabel(labelKey, currentLang.getCode()); ognlMessage = (label != null) ? label : labelKey; } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "getOgnlExpressionMessage"); throw new ApsSystemException("Error on extracting Ognl Expression Message", t); } return ognlMessage; } /** * Check the simple attribute when it is inserted as an element of a 'composite' attribute * in a monolist. * Extend this method in the helper class of the attribute which, due to its nature, * requires further checks other the "general" (standard) ones, * when inserted in a composite attribute of a monolist. * * @param action The action to fill with the proper error messages, if any. * @param attribute The current Attribute to check, both 'simple' or 'complex'. * @param tracer The 'tracer' class needed to find the position of the attribute inside a 'composite' one. * @param entity The entity to check. */ protected void checkMonoListCompositeElement(ActionSupport action, AttributeInterface attribute, AttributeTracer tracer, IApsEntity entity) { if (!this.isValidMonoListCompositeElement(attribute, tracer)) { this.addFieldError(action, attribute, tracer, this.getMonoListCompositeElementNotValidMessage(), null); } else if (attribute.isRequired() && this.getState(attribute, tracer) != VALUED_ATTRIBUTE_STATE) { this.addFieldError(action, attribute, tracer, this.getRequiredAttributeMessage(), null); } } /** * This method defines a standard of validity for an attribute inserted as an * element of a composite attribute in a monolist. * This method must be extended in all the attribute managers which must * enforce the standard validity checks with additional controls when inserting * a new element of a composite attribute in a monolist. * * @param attribute The current attribute to check, both 'simple' or 'complex'. * @param tracer The 'tracer' class needed to find the position of the attribute inside a 'composite' one. * @return true If the element is valid, false otherwise. */ protected boolean isValidMonoListCompositeElement(AttributeInterface attribute, AttributeTracer tracer) { return this.getState(attribute, tracer) != INCOMPLETE_ATTRIBUTE_STATE; } /** * This return the key of the message to return if the attribute of a composite * element in a monolist is not valid. * If the nature of the method requires particular message is necessary to override this method. * * @return The message to return. */ protected String getMonoListCompositeElementNotValidMessage() { return this.getInvalidAttributeMessage(); } /** * Check the 'simple' attribute when inserted as element in a monolist. * Extend this method in the helper class of the attribute which, due to its nature, * requires further checks other the "general" (standard) ones, * when inserted as element of a monolist. * * @param action The action to fill with the proper error messages, if any. * @param attribute The current attribute to check, both 'simple' or 'complex'. * @param tracer The 'tracer' class needed to find the position of the attribute inside a 'composite' one. * @param entity The entity to check. */ protected void checkMonoListElement(ActionSupport action, AttributeInterface attribute, AttributeTracer tracer, IApsEntity entity) { if (!this.isValidMonoListElement(attribute, tracer)) { this.addFieldError(action, attribute, tracer, this.getMonoListElementNotValidMessage(), null); } } /** * This method defines a standard of validity for an attribute inserted as an * element in a monolist. * This method must be extended in all the attribute managers which must * enforce the standard validity checks with additional controls when inserting * a new element in a monolist. * * @param attribute The current attribute to check, both 'simple' or 'complex'. * @param tracer The 'tracer' class needed to find the position of the attribute inside a 'composite' one. * @return true If the element is valid, false otherwise. */ protected boolean isValidMonoListElement(AttributeInterface attribute, AttributeTracer tracer) { return this.getState(attribute, tracer) == VALUED_ATTRIBUTE_STATE; } /** * This return the key of the message to return if the attribute element in a * monolist is not valid. If the nature of the method requires particular message * is necessary to override this method. * * @return The message to return. */ protected String getMonoListElementNotValidMessage() { return this.getInvalidAttributeMessage(); } /** * Check the validity of the simple attribute when it is inserted in a multi-language list. * Extend this method in the helper class of the attribute which, due to its nature, * requires further checks other the "general" (standard) ones, when inserted as * element of a multi-language list. * * @param action The action to fill with the proper error messages, if any. * @param attribute The current attribute to check, both 'simple' or 'complex'. * @param tracer The 'tracer' class needed to find the position of the attribute inside a 'composite' one. * @param entity The entity to check. */ protected void checkListElement(ActionSupport action, AttributeInterface attribute, AttributeTracer tracer, IApsEntity entity) { if (!this.isValidListElement(attribute, tracer)) { this.addFieldError(action, attribute, tracer, this.getListElementNotValidMessage(), null); } } /** * This method defines a standard of validity for an attribute inserted as an * element in a multi-language list. * This method must be extended in all the attribute managers which must * enforce the standard validity checks with additional controls when inserting * a new element in a multi-language list. * * @param attribute The current attribute to check, both 'simple' or 'complex'. * @param tracer The 'tracer' class needed to find the position of the attribute inside a 'composite' one. * @return true If the element is valid, false otherwise. */ protected boolean isValidListElement(AttributeInterface attribute, AttributeTracer tracer) { return this.getState(attribute, tracer) == VALUED_ATTRIBUTE_STATE; } /** * This return the key of the message to return if the attribute element in a * multi-language list is not valid. * If the nature of the method requires particular message is necessary to override this method. * * @return The message to return. */ protected String getListElementNotValidMessage() { return this.getInvalidAttributeMessage(); } /** * Return the key of the message to retrieve when an attribute is not valid. * If a customized message is needed eg. due to the nature of the attribute, extend this method. * @return The key of the message to return. */ protected String getInvalidAttributeMessage() { return "EntityAttribute.fieldError.invalidAttribute"; } /** * This method implements the validity criteria of an attribute, both simple or * complex; the only check performed here is whether the attribute is mandatory or not. * If the attribute needs further checks other than those by default, extend this method. * * @param action The action to fill with the proper error messages, if any. * @param attribute The current attribute to check, both 'simple' or 'complex'. * @param tracer The 'tracer' class needed to find the position of the attribute inside a 'composite' one. * @param entity The entity to check. */ protected void checkSingleAttribute(ActionSupport action, AttributeInterface attribute, AttributeTracer tracer, IApsEntity entity) { if (attribute.isRequired() && this.getState(attribute, tracer) == EMPTY_ATTRIBUTE_STATE) { this.addFieldError(action, attribute, tracer, this.getRequiredAttributeMessage(), null); } } /** * Return the key of the message used when a mandatory attribute is not populated. * If a customized message is needed eg. due to the nature of the attribute, extend this method. * * @return The key of the message to return. */ protected String getRequiredAttributeMessage() { return "EntityAttribute.fieldError.required"; } /** * Add an error message related to a field in a form. * * @param action The action where the error message is added * @param fieldName The name of the field. * @param messageKey The key of the error message. * @param args The arguments of the error message. */ protected void addFieldError(ActionSupport action, String fieldName, String messageKey, String[] args) { action.addFieldError(fieldName, action.getText(messageKey, args)); } /** * Add an error message related to a field in a form. * * @param action The action where the error message is added. * @param attribute The current attribute (simple or complex) to which the error message is appended. * @param tracer The 'tracer' class needed to find the position of the attribute inside a 'composite' one. * @param messageKey The key of the error message. * @param args The arguments of the error message. */ protected void addFieldError(ActionSupport action, AttributeInterface attribute, AttributeTracer tracer, String messageKey, String[] args) { String messageAttributePositionPrefix = this.createErrorMessageAttributePositionPrefix(action, attribute, tracer); String messageError = null; if (args != null) { messageError = action.getText(messageKey, args); } else { messageError = action.getText(messageKey); } String formFieldName = tracer.getFormFieldName(attribute); action.addFieldError(formFieldName, messageAttributePositionPrefix + " " + messageError); } private String createErrorMessageAttributePositionPrefix(ActionSupport action, AttributeInterface attribute, AttributeTracer tracer) { if (tracer.isMonoListElement()) { if (tracer.isCompositeElement()) { String[] args = {tracer.getParentAttribute().getName(), String.valueOf(tracer.getListIndex()+1), attribute.getName()}; return action.getText("EntityAttribute.compositeListAttributeElement.errorMessage.prefix", args); } else { String[] args = {attribute.getName(), String.valueOf(tracer.getListIndex()+1)}; return action.getText("EntityAttribute.monolistAttributeElement.errorMessage.prefix", args); } } else if (tracer.isCompositeElement()) { String[] args = {tracer.getParentAttribute().getName(), attribute.getName()}; return action.getText("EntityAttribute.compositeAttributeElement.errorMessage.prefix", args); } else if (tracer.isListElement()) { String[] args = {attribute.getName(), tracer.getListLang().getDescr(), String.valueOf(tracer.getListIndex()+1)}; return action.getText("EntityAttribute.listAttributeElement.errorMessage.prefix", args); } else { String[] args = {attribute.getName()}; return action.getText("EntityAttribute.singleAttribute.errorMessage.prefix", args); } } /** * Return the status of the current attribute. * The status can be: EMPTY, INCOMPLETE, POPULATED. * INCOMPLETE applies only to those simple attributes composed by two or more elements * (eg. the 'image' attribute is composed by a resource and a description). * This method applies to both simple and complex attributes. * This check does neither imply that the attribute is valid nor complete: eg. for * the 'number' attribute this method does not check for the given string to be a number, * but it rather checks for the presence of the string; in the List attribute at least one * element -whether correct or not- must be present. * * @param attribute The current attribute (simple or complex) to check. * @param tracer The 'tracer' class needed to find the position of the attribute inside a 'composite' one. * @return The code representing the current status */ protected abstract int getState(AttributeInterface attribute, AttributeTracer tracer); @Override public void updateEntityAttribute(AttributeInterface attribute, Map<String, AttributeManagerInterface> attributeManagers, HttpServletRequest request) { this.setAttributeManagers(attributeManagers); this.updateAttribute(attribute, new AttributeTracer(), request); } /** * Updates the attribute with the criteria specified in the content editing form. * * @param attribute The current attribute (simple or complex) to check. * @param tracer The 'tracer' class needed to find the position of the attribute inside a 'composite' one. * @param request The request. */ protected abstract void updateAttribute(AttributeInterface attribute, AttributeTracer tracer, HttpServletRequest request); /** * Return the value of the current attribute passed from the form. * * @param attribute The current attribute (simple or complex) to check. * @param tracer The 'tracer' class needed to find the position of the attribute inside a 'composite' one. * @param request The request. * @return The value passed in the form */ protected String getValueFromForm(AttributeInterface attribute, AttributeTracer tracer, HttpServletRequest request) { String formFieldName = tracer.getFormFieldName(attribute); return request.getParameter(formFieldName); } protected AttributeManagerInterface getManager(String typeCode) { AbstractAttributeManager manager = (AbstractAttributeManager) this.getAttributeManagers().get(typeCode); AbstractAttributeManager clone = null; try { Class attributeClass = Class.forName(manager.getClass().getName()); clone = (AbstractAttributeManager) attributeClass.newInstance(); } catch (Exception e) { throw new RuntimeException("Could not clone the attribute manager '" + this.getClass().getName() + "'"); } clone.setAttributeManagers(this.getAttributeManagers()); manager.setExtraPropertyTo(clone); return clone; } /** * Set the extra properties in the given manager. * This method is used when creating a manager to handle the attribute element of a complex * attribute and must be implemented when setting extra attributes. * @param manager The manager to create. */ protected void setExtraPropertyTo(AttributeManagerInterface manager) { //nothing to do } private Map<String, AttributeManagerInterface> getAttributeManagers() { return this._attributeManagers; } private void setAttributeManagers(Map<String, AttributeManagerInterface> attributeManagers) { this._attributeManagers = attributeManagers; } protected II18nManager getI18nManager() { return _i18nManager; } public void setI18nManager(II18nManager i18nManager) { this._i18nManager = i18nManager; } /** * Return the manager of the system languages. * @return The manager of the system languages. */ protected ILangManager getLangManager() { return _langManager; } /** * Set the manager of the system languages. * @param langManager The manager that handles the language. */ public void setLangManager(ILangManager langManager) { this._langManager = langManager; } private Map<String, AttributeManagerInterface> _attributeManagers; private II18nManager _i18nManager; private ILangManager _langManager; /** * Constant code describing the status of the empty attribute. */ protected final int EMPTY_ATTRIBUTE_STATE = 0; /** * Constant code describing the status of the incomplete attribute (not properly populated). Please note * that this status cannot be never accepted by the system. */ protected final int INCOMPLETE_ATTRIBUTE_STATE = 1; /** * Constant code describing the status of the valued attribute. */ protected final int VALUED_ATTRIBUTE_STATE = 2; }