com.evolveum.midpoint.common.validator.Validator.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.common.validator.Validator.java

Source

/*
 * Copyright (c) 2010-2013 Evolveum
 *
 * 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 com.evolveum.midpoint.common.validator;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.validation.Schema;

import org.apache.commons.lang.StringUtils;
import org.codehaus.staxmate.dom.DOMConverter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import com.evolveum.midpoint.prism.Objectable;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.schema.SchemaRegistry;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ResourceTypeUtil;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.JAXBUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;

/**
 * 
 * 
 * @author Radovan Semancik
 * 
 */
public class Validator {

    private static final Trace LOGGER = TraceManager.getTrace(Validator.class);
    private static final String INPUT_STREAM_CHARSET = "utf-8";
    private static final String OPERATION_PREFIX = Validator.class.getName() + ".";
    private static final String OPERATION_RESOURCE_NAMESPACE_CHECK = OPERATION_PREFIX + "resourceNamespaceCheck";
    private static final String OPERATION_RESOURCE_BASICS_CHECK = OPERATION_PREFIX + "objectBasicsCheck";
    private static final String START_LINE_NUMBER = "startLineNumber";
    private static final String END_LINE_NUMBER = "endLineNumber";
    private boolean verbose = false;
    private boolean validateSchemas = true;
    private boolean allowAnyType = false;
    private EventHandler handler;
    private DOMConverter domConverter = new DOMConverter();
    private Unmarshaller unmarshaller = null;
    private PrismContext prismContext;
    private Schema midPointJavaxSchema;
    private javax.xml.validation.Validator xsdValidator;
    long progress = 0;
    long errors = 0;
    long stopAfterErrors = 0;

    public Validator(PrismContext prismContext) {
        this.prismContext = prismContext;
        this.handler = null;
        initialize();
    }

    public Validator(PrismContext prismContext, EventHandler handler) {
        this.prismContext = prismContext;
        this.handler = handler;
        initialize();
    }

    private void initialize() {
        if (prismContext == null) {
            throw new IllegalStateException("No prism context set during validator initialization");
        }
        SchemaRegistry schemaRegistry = prismContext.getSchemaRegistry();
        midPointJavaxSchema = schemaRegistry.getJavaxSchema();
        xsdValidator = midPointJavaxSchema.newValidator();
        xsdValidator.setResourceResolver(schemaRegistry);
    }

    public EventHandler getHandler() {
        return handler;
    }

    public void setHandler(EventHandler handler) {
        this.handler = handler;
    }

    public PrismContext getPrismContext() {
        return prismContext;
    }

    public boolean getVerbose() {
        return verbose;
    }

    public void setVerbose(boolean verbose) {
        this.verbose = verbose;
    }

    public void setValidateSchema(boolean validateSchemas) {
        this.validateSchemas = validateSchemas;
    }

    public boolean getValidateSchema() {
        return validateSchemas;
    }

    public void setAllowAnyType(boolean allowAnyType) {
        this.allowAnyType = allowAnyType;
    }

    public boolean getAllowAnyType() {
        return allowAnyType;
    }

    public long getStopAfterErrors() {
        return stopAfterErrors;
    }

    public void setStopAfterErrors(long stopAfterErrors) {
        this.stopAfterErrors = stopAfterErrors;
    }

    public long getProgress() {
        return progress;
    }

    public long getErrors() {
        return errors;
    }

    public void validate(InputStream inputStream, OperationResult validatorResult,
            String objectResultOperationName) {

        XMLStreamReader stream = null;
        try {

            Map<String, String> rootNamespaceDeclarations = new HashMap<String, String>();

            XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
            stream = xmlInputFactory.createXMLStreamReader(inputStream);

            int eventType = stream.nextTag();
            if (eventType == XMLStreamConstants.START_ELEMENT) {
                if (!stream.getName().equals(SchemaConstants.C_OBJECTS)) {
                    // This has to be an import file with a single objects. Try
                    // to process it.
                    OperationResult objectResult = validatorResult.createSubresult(objectResultOperationName);
                    progress++;
                    objectResult.addContext(OperationResult.CONTEXT_PROGRESS, progress);

                    EventResult cont = null;
                    try {
                        cont = readFromStreamAndValidate(stream, objectResult, rootNamespaceDeclarations,
                                validatorResult);
                    } catch (RuntimeException e) {
                        // Make sure that unexpected error is recorded.
                        objectResult.recordFatalError(e);
                        throw e;
                    }

                    if (!cont.isCont()) {
                        String message = null;
                        if (cont.getReason() != null) {
                            message = cont.getReason();
                        } else {
                            message = "Object validation failed (no reason given)";
                        }
                        if (objectResult.isUnknown()) {
                            objectResult.recordFatalError(message);
                        }
                        validatorResult.recordFatalError(message);
                        return;
                    }
                    // return to avoid processing objects in loop
                    validatorResult.computeStatus("Validation failed", "Validation warnings");
                    return;
                }
                // Extract root namespace declarations
                for (int i = 0; i < stream.getNamespaceCount(); i++) {
                    rootNamespaceDeclarations.put(stream.getNamespacePrefix(i), stream.getNamespaceURI(i));
                }
            } else {
                throw new SystemException("StAX Malfunction?");
            }

            while (stream.hasNext()) {
                eventType = stream.next();
                if (eventType == XMLStreamConstants.START_ELEMENT) {

                    OperationResult objectResult = validatorResult.createSubresult(objectResultOperationName);
                    progress++;
                    objectResult.addContext(OperationResult.CONTEXT_PROGRESS, progress);

                    EventResult cont = null;
                    try {
                        // Read and validate individual object from the stream
                        cont = readFromStreamAndValidate(stream, objectResult, rootNamespaceDeclarations,
                                validatorResult);
                    } catch (RuntimeException e) {
                        if (objectResult.isUnknown()) {
                            // Make sure that unexpected error is recorded.
                            objectResult.recordFatalError(e);
                        }
                        throw e;
                    }

                    if (objectResult.isError()) {
                        errors++;
                    }

                    objectResult.cleanupResult();
                    validatorResult.summarize();

                    if (cont.isStop()) {
                        if (cont.getReason() != null) {
                            validatorResult.recordFatalError("Processing has been stopped: " + cont.getReason());
                        } else {
                            validatorResult.recordFatalError("Processing has been stopped");
                        }
                        // This means total stop, no other objects will be
                        // processed
                        return;
                    }
                    if (!cont.isCont()) {
                        if (stopAfterErrors > 0 && errors >= stopAfterErrors) {
                            validatorResult.recordFatalError("Too many errors (" + errors + ")");
                            return;
                        }
                    }
                }
            }

        } catch (XMLStreamException ex) {
            // validatorResult.recordFatalError("XML parsing error: " +
            // ex.getMessage()+" on line "+stream.getLocation().getLineNumber(),ex);
            validatorResult.recordFatalError("XML parsing error: " + ex.getMessage(), ex);
            if (handler != null) {
                handler.handleGlobalError(validatorResult);
            }
            return;
        }

        // Error count is sufficient. Detailed messages are in subresults
        validatorResult.computeStatus(errors + " errors, " + (progress - errors) + " passed");

    }

    private EventResult readFromStreamAndValidate(XMLStreamReader stream, OperationResult objectResult,
            Map<String, String> rootNamespaceDeclarations, OperationResult validatorResult) {

        objectResult.addContext(START_LINE_NUMBER, stream.getLocation().getLineNumber());

        Document objectDoc;
        try {
            // Parse the object from stream to DOM
            objectDoc = domConverter.buildDocument(stream);
        } catch (XMLStreamException ex) {
            validatorResult.recordFatalError("XML parsing error: " + ex.getMessage(), ex);
            if (handler != null) {
                handler.handleGlobalError(validatorResult);
            }
            objectResult.recordFatalError(ex);
            return EventResult.skipObject();
        }

        objectResult.addContext(END_LINE_NUMBER, stream.getLocation().getLineNumber());

        // This element may not have complete namespace definitions for a
        // stand-alone
        // processing, therefore copy namespace definitions from the root
        // element
        Element objectElement = DOMUtil.getFirstChildElement(objectDoc);
        DOMUtil.setNamespaceDeclarations(objectElement, rootNamespaceDeclarations);

        return validateObjectInternal(objectElement, objectResult, validatorResult);
    }

    public EventResult validateObject(String stringXml, OperationResult objectResult) {
        Document objectDoc = DOMUtil.parseDocument(stringXml);
        Element objectElement = DOMUtil.getFirstChildElement(objectDoc);
        return validateObjectInternal(objectElement, objectResult, objectResult);
    }

    public EventResult validateObject(Element objectElement, OperationResult objectResult) {
        return validateObjectInternal(objectElement, objectResult, objectResult);
    }

    private EventResult validateObjectInternal(Element objectElement, OperationResult objectResult,
            OperationResult validatorResult) {
        try {
            Node postValidationTree = null;

            if (validateSchemas) {
                postValidationTree = validateSchema(objectElement, objectResult);
                if (postValidationTree == null) {
                    // There was an error
                    return EventResult.skipObject(objectResult.getMessage());
                }
            }

            if (handler != null) {
                EventResult cont;
                try {
                    cont = handler.preMarshall(objectElement, postValidationTree, objectResult);
                } catch (RuntimeException e) {
                    objectResult.recordFatalError("Internal error: preMarshall call failed: " + e.getMessage(), e);
                    throw e;
                }
                if (!cont.isCont()) {
                    if (objectResult.isUnknown()) {
                        objectResult.recordFatalError("Stopped after preMarshall, no reason given");
                    }
                    return cont;
                }
            }

            if (!objectResult.isAcceptable()) {
                // Schema validation or preMarshall has failed. No point to
                // continue with this object.
                if (objectResult.isUnknown()) {
                    objectResult.recordFatalError("Result not acceptable after preMarshall, no reason given");
                }
                return EventResult.skipObject();
            }

            PrismObject<? extends Objectable> object = prismContext.parseObject(objectElement);

            try {
                object.checkConsistence();
            } catch (RuntimeException e) {
                objectResult.recordFatalError(
                        "Internal object inconsistence, probably a parser bug: " + e.getMessage(), e);
                return EventResult.skipObject();
            }

            Objectable objectType = null;
            if (object != null) {
                objectType = object.asObjectable();
            }

            if (verbose) {
                LOGGER.trace("Processing OID " + objectType.getOid());
            }

            objectResult.addContext(OperationResult.CONTEXT_OBJECT, objectType);

            validateObject(objectType, objectResult);

            if (handler != null) {
                EventResult cont;
                try {
                    cont = handler.postMarshall(object, objectElement, objectResult);
                } catch (RuntimeException e) {
                    // Make sure that unhandled exceptions are recorded in object result before they are rethrown
                    objectResult.recordFatalError("Internal error: postMarshall call failed: " + e.getMessage(), e);
                    throw e;
                }
                if (!cont.isCont()) {
                    if (objectResult.isUnknown()) {
                        objectResult.recordFatalError("Stopped after postMarshall, no reason given");
                    }
                    return cont;
                }
            }

            objectResult.recomputeStatus();

            return EventResult.cont();

        } catch (SchemaException ex) {
            if (verbose) {
                ex.printStackTrace();
            }
            if (handler != null) {
                try {
                    handler.handleGlobalError(validatorResult);
                } catch (RuntimeException e) {
                    // Make sure that unhandled exceptions are recorded in object result before they are rethrown
                    objectResult.recordFatalError(
                            "Internal error: handleGlobalError call failed: " + e.getMessage(), e);
                    throw e;
                }
            }
            objectResult.recordFatalError(ex);
            return EventResult.skipObject();
        } catch (RuntimeException ex) {
            validatorResult.recordFatalError("Couldn't parse object: " + ex.getMessage(), ex);
            if (verbose) {
                ex.printStackTrace();
            }
            if (handler != null) {
                try {
                    handler.handleGlobalError(validatorResult);
                } catch (RuntimeException e) {
                    // Make sure that unhandled exceptions are recorded in object result before they are rethrown
                    objectResult.recordFatalError(
                            "Internal error: handleGlobalError call failed: " + e.getMessage(), e);
                    throw e;
                }
            }
            objectResult.recordFatalError(ex);
            return EventResult.skipObject();
        }

    }

    // this was made public to allow validation of pre-parsed non-prism documents
    public Node validateSchema(Element objectDoc, OperationResult objectResult) {
        OperationResult result = objectResult.createSubresult(Validator.class.getName() + ".validateSchema");
        DOMResult validationResult = new DOMResult();
        try {
            xsdValidator.validate(new DOMSource(objectDoc), validationResult);
        } catch (SAXException e) {
            result.recordFatalError("Validation error: " + e.getMessage(), e);
            objectResult.computeStatus("Validation error: " + e.getMessage());
            return null;
        } catch (IOException e) {
            result.recordFatalError("IO error during validation: " + e.getMessage(), e);
            objectResult.computeStatus("IO error during validation: " + e.getMessage());
            return null;
        }
        result.recordSuccess();
        return validationResult.getNode();
    }

    public void validateObject(Objectable object, OperationResult objectResult) {
        // Check generic object properties

        checkBasics(object, objectResult);

        // Type-specific checks

        if (object instanceof ResourceType) {
            ResourceType resource = (ResourceType) object;
            checkResource(resource, objectResult);
        }

        // TODO: more checks

        objectResult.recomputeStatus("Object validation has failed", "Validation warning");
        objectResult.recordSuccessIfUnknown();

    }

    // BIG checks - checks that create subresults

    void checkBasics(Objectable object, OperationResult objectResult) {
        OperationResult subresult = objectResult.createSubresult(OPERATION_RESOURCE_BASICS_CHECK);
        checkName(object, object.getName(), "name", subresult);
        subresult.recordSuccessIfUnknown();
    }

    void checkResource(ResourceType resource, OperationResult objectResult) {
        OperationResult subresult = objectResult.createSubresult(OPERATION_RESOURCE_NAMESPACE_CHECK);
        checkUri(resource, ResourceTypeUtil.getResourceNamespace(resource), "namespace", subresult);
        subresult.recordSuccessIfUnknown();
    }

    // Small checks - checks that don't create subresults

    void checkName(Objectable object, PolyStringType value, String propertyName, OperationResult subResult) {
        // TODO: check for all whitespaces
        // TODO: check for bad characters
        if (value == null) {
            error("Null property", object, propertyName, subResult);
            return;
        }
        String orig = value.getOrig();
        if (orig == null || orig.isEmpty()) {
            error("Empty property", object, propertyName, subResult);
        }
    }

    void checkUri(Objectable object, String value, String propertyName, OperationResult subResult) {
        // TODO: check for all whitespaces
        // TODO: check for bad characters
        if (StringUtils.isEmpty(value)) {
            error("Empty property", object, propertyName, subResult);
            return;
        }
        try {
            URI uri = new URI(value);
            if (uri.getScheme() == null) {
                error("URI is supposed to be absolute", object, propertyName, subResult);
            }
        } catch (URISyntaxException ex) {
            error("Wrong URI syntax: " + ex, object, propertyName, subResult);
        }

    }

    void error(String message, Objectable object, OperationResult subResult) {
        subResult.addContext(OperationResult.CONTEXT_OBJECT, object);
        subResult.recordFatalError(message);
    }

    void error(String message, Objectable object, String propertyName, OperationResult subResult) {
        subResult.addContext(OperationResult.CONTEXT_OBJECT, object);
        subResult.addContext(OperationResult.CONTEXT_ITEM, propertyName);
        subResult.recordFatalError("<" + propertyName + ">: " + message);
    }

}