All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cristalise.kernel.persistency.outcomebuilder.field.StringField Maven / Gradle / Ivy

/**
 * This file is part of the CRISTAL-iSE XPath Outcome Initiator module.
 * Copyright (c) 2001-2016 The CRISTAL Consortium. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * http://www.fsf.org/licensing/licenses/lgpl.html
 */
package org.cristalise.kernel.persistency.outcomebuilder.field;

import static org.cristalise.kernel.persistency.outcomebuilder.SystemProperties.*;
import static org.cristalise.kernel.persistency.outcomebuilder.SystemProperties.Webui_inputField_string_defaultValue;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.cristalise.kernel.persistency.outcomebuilder.InvalidOutcomeException;
import org.cristalise.kernel.persistency.outcomebuilder.OutcomeStructure;
import org.cristalise.kernel.persistency.outcomebuilder.StructuralException;
import org.cristalise.kernel.persistency.outcomebuilder.StructureWithAppInfo;
import org.exolab.castor.types.AnyNode;
import org.exolab.castor.xml.schema.Annotated;
import org.exolab.castor.xml.schema.AttributeDecl;
import org.exolab.castor.xml.schema.ElementDecl;
import org.exolab.castor.xml.schema.Facet;
import org.exolab.castor.xml.schema.SimpleType;
import org.exolab.castor.xml.schema.SimpleTypesFactory;
import org.exolab.castor.xml.schema.Structure;
import org.exolab.castor.xml.schema.XMLType;
import org.exolab.castor.xml.schema.simpletypes.ListType;
import org.json.JSONObject;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import org.w3c.dom.Text;

import lombok.Getter;

/**
 * Superclass for the entry field for Field and AttributeList.
 */
public class StringField extends StructureWithAppInfo {

    private static final String[] strFields = {"mask", "placeholder"};
    private static final String[] excFields = {"pattern", "errmsg","container", "control", "labelGrid"};

    Node       data;
    Annotated  model;
    boolean    isValid  = true;
    boolean    isAttribute = false;
    String     name;
    SimpleType contentType;
    String     text;
    String     defaultValue;

    String     container = Webui_NgDynamicForms_FieldLayout_gridContainerClass.getString();
    String     control   = Webui_NgDynamicForms_FieldLayout_gridControlClass.getString();
    String     labelGrid = Webui_NgDynamicForms_FieldLayout_gridLabelClass.getString();

    @Getter
    Class javaType = String.class;

    /**
     * Javascript regexp pattern to validate field value in DynamicForms. It is either provided in the AppInfo.pattern field 
     * or it is computed from data available in XSD restrictions or in various AppInfo fields (check subclasses)
     */
    String pattern;
    /**
     * Error message to show to the user for validation errors
     */
    String errmsg;

    public StringField() {
        this(Arrays.asList(strFields), Arrays.asList(excFields));
    }

    public StringField(List strFields, List excFields) {
        super(strFields, excFields);
    }

    /**
     * 
     * @param model
     * @return
     */
    public static SimpleType getFieldType(Annotated  model) {
        if (model instanceof ElementDecl) {
            XMLType baseType = ((ElementDecl)model).getType();

            while (!(baseType instanceof SimpleType)) baseType = baseType.getBaseType();

            return (SimpleType)baseType;
        }
        else if (model instanceof AttributeDecl) {
            return ((AttributeDecl)model).getSimpleType();
        }

        return null;
    }

    /**
     * 
     * @param model
     * @return
     */
    private static StringField getFieldForType(Annotated  model) {
        SimpleType type = getFieldType(model);

        // handle lists special
        if (type instanceof ListType) return new ArrayField(type.getBuiltInBaseType());

        // is a combobox
        AnyNode appInfoNode = StructureWithAppInfo.getAppInfoNode(model, "listOfValues");
        if (type.hasFacet(Facet.ENUMERATION) || appInfoNode != null) return new ComboField(type, appInfoNode);

        // find info on length before we go to the base type
        long length = -1;
        if      (type.getLength()    != null) length = type.getLength().longValue();
        else if (type.getMaxLength() != null) length = type.getMaxLength().longValue();
        else if (type.getMinLength() != null) length = type.getMinLength().longValue();

        // find base type if derived
        if (!type.isBuiltInType()) type = type.getBuiltInBaseType();

        // derive the class from type
        Class contentClass = OutcomeStructure.getJavaClass(type.getTypeCode());

        if      (contentClass.equals(Boolean.class))        return new BooleanField();
        else if (contentClass.equals(BigInteger.class))     return new IntegerField();
        else if (contentClass.equals(BigDecimal.class))     return new DecimalField();
        else if (contentClass.equals(LocalDate.class))      return new DateField();
        else if (contentClass.equals(OffsetTime.class))     return new TimeField();
        else if (contentClass.equals(OffsetDateTime.class)) return new DateTimeField();
        else if (length > 60)                               return new LongStringField();
        else                                                return new StringField();
    }

    public static StringField getField(AttributeDecl model) throws StructuralException {
        if (model.isReference()) model = model.getReference();

        StringField newField = getFieldForType(model);
        newField.setDecl(model);

        return newField;
    }

    public static StringField getField(ElementDecl model) throws StructuralException {
        try {
            StringField newField = getFieldForType(model);

            newField.setDecl(model);
            return newField;
        }
        catch (Exception ex) {
            throw new StructuralException("No type defined in model");
        }
    }

    public void setDecl(AttributeDecl attrModel) throws StructuralException {
        this.model = attrModel;
        this.name = attrModel.getName();
        this.defaultValue = attrModel.getDefaultValue();
        this.contentType = attrModel.getSimpleType();
        this.isAttribute = true;
    }

    public void setDecl(ElementDecl elementModel) throws StructuralException {
        this.model = elementModel;
        this.name = elementModel.getName();
        this.defaultValue = elementModel.getDefaultValue();
        this.isAttribute = false;

        XMLType type = elementModel.getType();

        // derive base type
        if (type.isSimpleType()) this.contentType = (SimpleType) type;
        else                     this.contentType = (SimpleType) (type.getBaseType());

        if (this.contentType == null) throw new StructuralException("No declared base type of element");
    }

    public void setData(Attr newData) throws StructuralException {
        if (!(newData.getName().equals(name)))
            throw new StructuralException("Tried to add a " + newData.getName() + " into a " + name + " attribute.");

        this.data = newData;
        setText(newData.getValue());
    }

    public void setData(Text newData) {
        String contents = newData.getData();
        this.data = newData;
        setText(contents);
    }

    public void setData(String newData) throws InvalidOutcomeException {
        if (data == null) throw new InvalidOutcomeException("TextNode for '"+name+ "' does not exists");
        setText(newData);
        updateNode();
    }

    /**
     * Overwrite this method to provide conversion code for each type
     * 
     * @param value the Object to be converted
     * @throws InvalidOutcomeException
     */
    public void setValue(Object value) throws InvalidOutcomeException {
        setData(value.toString());
    }

    public boolean isOptional() {
        if (isAttribute) return ((AttributeDecl)model).isOptional();
        else             return ((ElementDecl)model).getMinOccurs() == 0;
    }

    public boolean hasValidator() {
        return  contentType.hasFacet(Facet.MIN_EXCLUSIVE)
             || contentType.hasFacet(Facet.MAX_EXCLUSIVE)
             || contentType.hasFacet(Facet.MIN_INCLUSIVE)
             || contentType.hasFacet(Facet.MAX_INCLUSIVE)
             || contentType.hasFacet(Facet.MIN_LENGTH)
             || contentType.hasFacet(Facet.MAX_LENGTH)
             || contentType.hasFacet(Facet.LENGTH)
             || contentType.hasFacet(Facet.TOTALDIGITS)
             || contentType.hasFacet(Facet.FRACTIONDIGITS)
             || getFieldType(model).getTypeCode() == SimpleTypesFactory.DECIMAL_TYPE //always generated for decimal field
             || StringUtils.isNotBlank(pattern);
    }

    public Structure getModel() {
        return model;
    }

    public String getName() {
        return name;
    }

    public Node getData() {
        return data;
    }

    /**
     * Returns the default value that is specific to the type, and required to create a valid xml.
     * 
     * @return zero length String
     */
    public String getDefaultValue() {
        return Webui_inputField_string_defaultValue.getString();
    }

    public void updateNode() {
        if (data == null) return;

        if (data instanceof Text) ((Text) data).setData(getText());
        else                      ((Attr) data).setValue(getText());
    }

    public String getText() {
        return text;
    }

    public void setText(String value) {
        text = value;
    }

    public String getNgDynamicFormsControlType() {
        return "INPUT";
    }

    public void setNgDynamicFormsValidators(JSONObject validators) {
        if (StringUtils.isNotBlank(pattern)) {
            validators.put("pattern", pattern);
        }

        if(contentType.hasFacet(Facet.MIN_LENGTH)) {
            Facet minLength = contentType.getFacet(Facet.MIN_LENGTH);
            validators.put(minLength.getName(), minLength.getValue());
        }

        if(contentType.hasFacet(Facet.MAX_LENGTH)) {
            Facet maxLength = contentType.getFacet(Facet.MAX_LENGTH);
            validators.put(maxLength.getName(), maxLength.getValue());
        }

        if(contentType.hasFacet(Facet.LENGTH)) {
            Facet length = contentType.getFacet(Facet.LENGTH);
            validators.put("minLength", length.getValue());
            validators.put("maxLength", length.getValue());
        }
    }

    public void setNgDynamicFormsErrorMessages(JSONObject errorMessages) {
        if (StringUtils.isNotBlank(errmsg)) {
            errorMessages.put("pattern", errmsg);
        }
    }

    public JSONObject generateNgDynamicFormsCls() {
        JSONObject fieldCls = new JSONObject();

        JSONObject fieldElement = new JSONObject();
        fieldElement.put("label", Webui_NgDynamicForms_FieldLayout_elementLabelClass.getString());

        JSONObject fieldGrid = new JSONObject();
        fieldGrid.put("container", container);
        fieldGrid.put("label",     labelGrid);
        fieldGrid.put("control",   control);

        fieldCls.put("element", fieldElement);
        fieldCls.put("grid",    fieldGrid);

        return fieldCls;
    }
    
    @Override
    protected void setAppInfoDynamicFormsExceptionValue(String name, String value) {
        if (name.equals("pattern")) {
            pattern = value;
        }
        else if (name.equals("errmsg")) {
            errmsg = value;
        }
        else if (name.equals("container")) {
            container = value;
        }
        else if (name.equals("control")) {
            control = value;
        }
        else if (name.equals("labelGrid")) {
            labelGrid = value;
        }
    }
    
    /**
     * PrimeNG specific settings are stored in 'additional' JSONOBject of NgDynamicForms configs
     * 
     * @param field the actual config field 
     * @return the 'additional' JSONOBject attached to the config field
     */
    public JSONObject getNgDynamicFormsAdditional(JSONObject field) {
        if (field.has("additional")) {
            return (JSONObject) field.get("additional");
        }
        else {
            JSONObject additional = new JSONObject();
            field.put("additional", additional);
            return additional;
        }
    }

    /**
     * 
     * @param field
     */
    public void updateWithAdditional(JSONObject field) {
        if (additional != null) {
            JSONObject fieldAdditional = getNgDynamicFormsAdditional(field);
            for (String key: additional.keySet()) fieldAdditional.put(key, additional.get(key));
        }
    }

    /**
     * 
     * @return
     */
    public JSONObject getNgDynamicFormsCommonFields(boolean withModel, boolean withLayout) {
        JSONObject field = new JSONObject();

        field.put("id",       name);
        // appinfo/dynamicForms could update label later, so do the CamelCase splitting now
        field.put("label",    StringUtils.join(StringUtils.splitByCharacterTypeCamelCase(name), " "));
        field.put("type",     getNgDynamicFormsControlType());
        field.put("required", !isOptional());

        //This can overwrite values set earlier, for example 'type' can be changed from INPUT to RATING or label can be provided
        setAppInfoDynamicFormsJson(model, field, false);

        if (withLayout) field.put("cls", generateNgDynamicFormsCls());

        JSONObject validators = new JSONObject();
        field.put("validators", validators);

        JSONObject errorMessages = new JSONObject();
        field.put("errorMessages", errorMessages);

        boolean required = field.getBoolean("required");

        if (!isOptional() && required) validators.put("required", JSONObject.NULL);

        if (hasValidator()) {
            setNgDynamicFormsValidators(validators);
            setNgDynamicFormsErrorMessages(errorMessages);
        }

        // appinfo/dynamicForms could have updated label, so do some fixing now
        String label = field.getString("label");
        label.replaceAll(" *", " ");
        field.put("label", label + (required ? " *": ""));

        //Put label as placholder if it was not specified in the Schema
        if (! field.has("placeholder")) field.put("placeholder", label);

        String defaultAutoComplete = Webui_autoComplete_default.getString();

        // autoComplete=on by default in NgDyanmicForms so no need to set
        if (! field.has("autoComplete") && defaultAutoComplete.equals("off") ) {
            field.put("autoComplete", defaultAutoComplete);
        }

        // if validators has no elements then remove it.
        if (field.getJSONObject("validators").length() == 0) {
            field.remove("validators");
        }
        // if errorMessages has no elements then remove it.
        if (field.getJSONObject("errorMessages").length() == 0) {
            field.remove("errorMessages");
        }

        return field;
    }

    public JSONObject generateNgDynamicForms(Map inputs, boolean withModel, boolean withLayout) {
        JSONObject input = getNgDynamicFormsCommonFields(withModel, withLayout);

        // AppInfo could set the 'inputType' to password already
        if (!input.has("inputType")) input.put("inputType", "text");

        return input;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy