de.betterform.xml.xforms.model.constraints.Validator.java Source code

Java tutorial

Introduction

Here is the source code for de.betterform.xml.xforms.model.constraints.Validator.java

Source

/*
 * Copyright (c) 2012. betterFORM Project - http://www.betterform.de
 * Licensed under the terms of BSD License
 */

package de.betterform.xml.xforms.model.constraints;

import de.betterform.xml.dom.DOMUtil;
import de.betterform.xml.ns.NamespaceConstants;
import de.betterform.xml.ns.NamespaceResolver;
import de.betterform.xml.xforms.exception.XFormsException;
import de.betterform.xml.xforms.model.Instance;
import de.betterform.xml.xforms.model.Model;
import de.betterform.xml.xforms.model.ModelItem;
import de.betterform.xml.xforms.xpath.saxon.function.XPathFunctionContext;
import net.sf.saxon.trans.XPathException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xerces.impl.dv.InvalidDatatypeValueException;
import org.apache.xerces.impl.dv.ValidatedInfo;
import org.apache.xerces.impl.dv.XSSimpleType;
import org.apache.xerces.impl.dv.xs.XSSimpleTypeDecl;
import org.apache.xerces.impl.validation.ValidationState;
import org.apache.xerces.impl.xs.XSComplexTypeDecl;
import org.apache.xerces.util.NamespaceSupport;
import org.apache.xerces.xs.XSConstants;
import org.w3c.dom.Node;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Validates instance data items.
 *
 * @author Ulrich Nicolas Lissé
 * @version $Id: Validator.java 3508 2008-08-31 10:03:03Z lars $
 */
public class Validator {

    private static Log LOGGER = LogFactory.getLog(Validator.class);

    private static String TYPE_STRING = "string";
    private static String TYPE_NS_STRING = NamespaceResolver.expand(NamespaceConstants.XFORMS_NS,
            Validator.TYPE_STRING);

    //XML Schema datatypes that are not supported -> 5.1 XML Schema Built-in Datatypes
    private static String TYPE_DURATION = "duration";
    private static String TYPE_NS_DURATION = NamespaceResolver.expand(NamespaceConstants.XMLSCHEMA_NS,
            Validator.TYPE_DURATION);
    private static String TYPE_ENTITY = "ENTITY";
    private static String TYPE_NS_ENTITY = NamespaceResolver.expand(NamespaceConstants.XMLSCHEMA_NS,
            Validator.TYPE_ENTITY);
    private static String TYPE_ENTITIES = "ENTITIES";
    private static String TYPE_NS_ENTITIES = NamespaceResolver.expand(NamespaceConstants.XMLSCHEMA_NS,
            Validator.TYPE_ENTITIES);
    private static String TYPE_NOTATION = "NOTATION";
    private static String TYPE_NS_NOTATION = NamespaceResolver.expand(NamespaceConstants.XMLSCHEMA_NS,
            Validator.TYPE_NOTATION);

    private Model model;
    private Map datatypes;
    private ValidatorMode mode;

    /**
     * Creates a new Validator.
     */
    public Validator() {
        // set validator mode
        this.mode = new OptimizedValidatorMode();
    }

    /**
     * Returns the Model of this Validator.
     *
     * @return the Model of this Validator.
     */
    public Model getModel() {
        return this.model;
    }

    /**
     * Sets the Model of this Validator.
     *
     * @param model the Model of this Validator.
     */
    public void setModel(Model model) {
        this.model = model;
    }

    /**
     * Returns the Schema Datatype definitions of this Validator.
     *
     * @return the Schema Datatype definitions of this Validator.
     */
    public Map getDatatypes() {
        return this.datatypes;
    }

    /**
     * Sets the Schema Datatype definitions of this Validator.
     *
     * @param datatypes the Schema Datatype definitions of this Validator.
     */
    public void setDatatypes(Map datatypes) {
        this.datatypes = datatypes;
    }

    /**
     * Checks wether the specified Schema Datatype definition is known by this
     * Validator.
     *
     * @param name the name of the Schema Datatype definition.
     * @return <code>true</code> if the specified Schema Datatype definition is
     *         known, otherwise <code>false</code>.
     */
    public boolean isKnown(String name) {
        if (this.datatypes == null) {
            return false;
        }

        String expandedName = NamespaceResolver.getExpandedName(this.model.getElement(), name);
        return this.datatypes.get(expandedName) != null;
    }

    /**
     * Checks wether the specified Schema Datatype definition is supported by
     * this Validator.
     *
     * @param name the name of the Schema Datatype definition.
     * @return <code>true</code> if the specified Schema Datatype definition is
     *         supported, otherwise <code>false</code>.
     */
    public boolean isSupported(String name) {
        String expandedName = NamespaceResolver.getExpandedName(this.model.getElement(), name);

        return !(TYPE_DURATION.equals(expandedName) || TYPE_NS_DURATION.equals(expandedName)
                || TYPE_ENTITY.equals(expandedName) || TYPE_NS_ENTITY.equals(expandedName)
                || TYPE_ENTITIES.equals(expandedName) || TYPE_NS_ENTITIES.equals(expandedName)
                || TYPE_NOTATION.equals(expandedName) || TYPE_NS_NOTATION.equals(expandedName));

    }

    /**
     * Checks wether the Schema Datatype definition specified by the
     * <code>restriction</code> parameter is derived by restriction from the
     * Schema Datatype definition specified by the <code>base</code> parameter.
     *
     * @param base the restriction of the base Schema Datatype definition.
     * @param restriction the restriction of the restricted Schema Datatype
     * definition.
     * @return <code>true</code> if both specified Schema Datatype definitions
     *         are known by this Validator and the second definition is derived
     *         by restriction from the first, otherwise <code>false</code>.
     */
    public boolean isRestricted(String base, String restriction) {
        if (this.datatypes == null) {
            return false;
        }

        String baseName = NamespaceResolver.getExpandedName(this.model.getElement(), base);
        XSSimpleType baseType = (XSSimpleType) this.datatypes.get(baseName);
        if (baseType == null) {
            return false;
        }

        String restrictionName = NamespaceResolver.getExpandedName(this.model.getElement(), restriction);
        Object restrictionType = this.datatypes.get(restrictionName);
        if (restrictionType == null) {
            return false;
        }

        if (restrictionType instanceof XSSimpleTypeDecl) {
            return ((XSSimpleTypeDecl) restrictionType).isDOMDerivedFrom(baseType.getNamespace(),
                    baseType.getName(), XSConstants.DERIVATION_LIST);
        } else if (restrictionType instanceof XSComplexTypeDecl) {
            return ((XSComplexTypeDecl) restrictionType).isDOMDerivedFrom(baseType.getNamespace(),
                    baseType.getName(), XSConstants.DERIVATION_LIST);
        }
        return ((XSSimpleType) restrictionType).derivedFromType(baseType, XSConstants.DERIVATION_RESTRICTION);
    }

    /**
     * Validates the specified instance data nodes.
     *
     * @param instance the instance to be validated.
     * @throws XPathException 
     */
    public void validate(Instance instance) throws XFormsException {
        // during init validate model items in default mode: no items are
        // ignored and nothing discontinues validation. once the model is ready,
        // model items are validated in optzimized mode which simply skips
        // all unmodified model items.
        validate(instance, instance.getInstanceNodeset(), 1, "/", Collections.EMPTY_MAP, null,
                this.model.isReady() ? this.mode : new DefaultValidatorMode());
    }

    /**
     * Validates the specified instance data node and its descendants.
     *
     * @param instance the instance to be validated.
     * @param path an xpath selecting an arbitrary subtree of the instance.
     * @param mode a validator mode steering the validation process.
     * @throws XPathException 
     */
    public void validate(Instance instance, List instanceNodeset, int position, String path, Map prefixMapping,
            XPathFunctionContext xpathFunctionContext, ValidatorMode mode) throws XFormsException {
        if (LOGGER.isDebugEnabled()) {
            //XXX fix logging
            LOGGER.debug("validate Instance '" + instance.getId() + "' in " + mode + " mode");
        }

        Iterator iterator = instance.iterateModelItems(instanceNodeset, position, path, prefixMapping,
                xpathFunctionContext, true);
        ModelItem modelItem;
        while (iterator.hasNext()) {
            modelItem = (ModelItem) iterator.next();

            if (mode.performValidation(modelItem)) {
                validate(modelItem);
            }

            if (!mode.continueValidation(modelItem)) {
                return;
            }
        }
    }

    /**
     * Validates the specified model item.
     *
     * @param modelItem the model item to be validated.
     */
    public void validate(ModelItem modelItem) {
        // obtain value to be checked
        String value = modelItem.getValue();

        // check for nillable type
        if (modelItem.isXSINillable() && (value == null || value.length() == 0)) {
            if (LOGGER.isTraceEnabled()) {
                String xsiPrefix = NamespaceResolver.getPrefix(this.model.getElement(),
                        NamespaceConstants.XMLSCHEMA_INSTANCE_NS);
                LOGGER.trace("validate: item " + modelItem.getNode() + " with @" + xsiPrefix
                        + ":nil='true' considered valid");
            }

            // item is considered valid regardless which type it has
            modelItem.getLocalUpdateView().setDatatypeValid(true);
            return;
        }

        // compute datatype validity
        boolean datatypeValid = true;

        // first check xsi:type ...
        String xsiType = expandName(modelItem.getXSIType());
        if (xsiType != null && !isDefaultType(xsiType) && isKnown(modelItem.getXSIType())) {
            // only check non-string datatypes
            datatypeValid = checkDatatype(xsiType, value);
        }

        // ... then check xf:type
        String xfType = expandName(modelItem.getDeclarationView().getDatatype());
        if (xfType != null && !isDefaultType(xfType)) {
            // only check non-string datatypes
            datatypeValid &= checkDatatype(xfType, value);
        }

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("validate: " + DOMUtil.getCanonicalPath((Node) modelItem.getNode()) + " computed "
                    + (datatypeValid ? "valid" : "INVALID"));
        }

        // set datatype validity
        modelItem.getLocalUpdateView().setDatatypeValid(datatypeValid);
    }

    private String expandName(String datatype) {
        return datatype != null ? NamespaceResolver.getExpandedName(this.model.getElement(), datatype) : null;
    }

    private boolean isDefaultType(String expandedName) {
        return TYPE_STRING.equals(expandedName) || TYPE_NS_STRING.equals(expandedName);
    }

    private boolean checkDatatype(String expandedName, String value) {
        XSSimpleType simpleType = (XSSimpleType) this.datatypes.get(expandedName);
        ValidatedInfo validatedInfo = new ValidatedInfo();
        ValidationState validationState = new ValidationState();
        validationState.setFacetChecking(true);
        validationState.setExtraChecking(false);
        validationState.setUsingNamespaces(true);

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("checking datatype - expandedName: " + expandedName);
        }
        // TODO: does not yet work with restricted QNames
        if (expandedName.endsWith("QName") && value.indexOf(":") != -1) {
            NamespaceSupport support = new NamespaceSupport();
            support.pushContext();
            String nsPrefix = value.substring(0, value.indexOf(":")).intern();
            Map namespaces = NamespaceResolver.getAllNamespaces(this.model.getElement());
            String nsURI = (String) namespaces.get(nsPrefix);

            if (nsPrefix != null && nsURI != null) {
                support.declarePrefix(nsPrefix, nsURI);
                validationState.setNamespaceSupport(support);
            }
        }

        //TODO: handle nullpointer see Testcase 8.1.4.b
        try {
            simpleType.validate(value.intern(), validationState, validatedInfo);
        } catch (InvalidDatatypeValueException e) {
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("value '" + value + "' of type " + expandedName + " is invalid - " + e.getMessage());
            }
            return false;
        }

        return true;
    }
}

// end of class