net.harawata.mybatipse.mybatis.XmlValidator.java Source code

Java tutorial

Introduction

Here is the source code for net.harawata.mybatipse.mybatis.XmlValidator.java

Source

/*-****************************************************************************** 
 * Copyright (c) 2014 Iwao AVE!.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Iwao AVE! - initial API and implementation and/or initial documentation
 *******************************************************************************/

package net.harawata.mybatipse.mybatis;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import javax.xml.xpath.XPathExpressionException;

import net.harawata.mybatipse.Activator;
import net.harawata.mybatipse.bean.BeanPropertyCache;
import net.harawata.mybatipse.util.XpathUtil;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.validation.AbstractValidator;
import org.eclipse.wst.validation.ValidationResult;
import org.eclipse.wst.validation.ValidationState;
import org.eclipse.wst.validation.internal.provisional.core.IReporter;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * @author Iwao AVE!
 */
@SuppressWarnings("restriction")
public class XmlValidator extends AbstractValidator {
    public static final String MARKER_ID = "net.harawata.mybatipse.XmlProblem";

    public static final String MISSING_TYPE = "missingType";

    public static final String NO_WRITABLE_PROPERTY = "noWritableProperty";

    public static final String MISSING_TYPE_HANDLER = "missingTypeHandler";

    public static final String MISSING_STATEMENT_METHOD = "missingStatementMethod";

    public static final String MISSING_RESULT_MAP = "missingResultMap";

    public static final String MISSING_SQL = "missingSql";

    public static final String MISSING_NAMESPACE = "missingNamespace";

    public static final String NAMESPACE_MANDATORY = "namespaceMandatory";

    public static final String DEPRECATED = "deprecated";

    private static final List<String> validatableTags = Arrays.asList("id", "idArg", "result", "arg", "resultMap",
            "collection", "association", "select", "insert", "update", "delete", "include", "cache", "typeAlias",
            "typeHandler", "objectFactory", "objectWrapperFactory", "plugin", "transactionManager", "mapper",
            "package", "databaseIdProvider");;

    public void cleanup(IReporter reporter) {
        // Nothing to do.
    }

    @Override
    public ValidationResult validate(final IResource resource, int kind, ValidationState state,
            IProgressMonitor monitor) {
        if (resource.getType() != IResource.FILE)
            return null;
        ValidationResult result = new ValidationResult();
        final IReporter reporter = result.getReporter(monitor);
        validateFile((IFile) resource, reporter, result);
        return result;
    }

    private void validateFile(IFile file, IReporter reporter, ValidationResult result) {
        if ((reporter != null) && (reporter.isCancelled() == true)) {
            throw new OperationCanceledException();
        }
        IStructuredModel model = null;
        try {
            file.deleteMarkers(MARKER_ID, false, IResource.DEPTH_ZERO);
            model = StructuredModelManager.getModelManager().getModelForRead(file);
            IDOMModel domModel = (IDOMModel) model;
            IDOMDocument domDoc = domModel.getDocument();
            NodeList nodes = domDoc.getChildNodes();

            IJavaProject project = JavaCore.create(file.getProject());

            for (int k = 0; k < nodes.getLength(); k++) {
                Node child = nodes.item(k);
                if (child instanceof IDOMElement) {
                    validateElement(project, (IDOMElement) child, file, domDoc, reporter, result);
                }
            }
        } catch (Exception e) {
            Activator.log(Status.WARNING, "Error occurred during validation.", e);
        } finally {
            if (model != null) {
                model.releaseFromRead();
            }
        }
    }

    private void validateElement(IJavaProject project, IDOMElement element, IFile file, IDOMDocument doc,
            IReporter reporter, ValidationResult result) throws JavaModelException, XPathExpressionException {
        if ((reporter != null) && (reporter.isCancelled() == true)) {
            throw new OperationCanceledException();
        }
        if (element == null)
            return;

        String tagName = element.getNodeName();

        if (validatableTags.contains(tagName)) {
            NamedNodeMap attrs = element.getAttributes();
            for (int i = 0; i < attrs.getLength(); i++) {
                IDOMAttr attr = (IDOMAttr) attrs.item(i);
                String attrName = attr.getName();
                String attrValue = attr.getValue().trim();

                // TODO: proxyFactory, logImpl, package
                if (("type".equals(attrName) && !"dataSource".equals(tagName)) || "resultType".equals(attrName)
                        || "parameterType".equals(attrName) || "ofType".equals(attrName)
                        || "typeHandler".equals(attrName) || "handler".equals(attrName)
                        || "interceptor".equals(attrName) || "class".equals(attrName)) {
                    // TODO: verify interface?
                    validateJavaType(project, file, doc, attr, attrValue, result, reporter);
                } else if ("property".equals(attrName)) {
                    validateProperty(element, file, doc, result, project, attr, attrValue, reporter);
                } else if ("id".equals(attrName) && ("select".equals(tagName) || "update".equals(tagName)
                        || "insert".equals(tagName) || "delete".equals(tagName))) {
                    validateStatementId(element, file, doc, result, project, attr, attrValue);
                } else if ("resultMap".equals(attrName) || "resultMap".equals(attrName)) {
                    validateResultMapId(project, file, doc, result, attr, attrValue, reporter);
                } else if ("refid".equals(attrName)) {
                    validateSqlId(project, file, doc, result, attr, attrValue, reporter);
                } else if ("select".equals(attrName)) {
                    validateSelectId(project, file, doc, result, attr, attrValue, reporter);
                } else if ("namespace".equals(attrName)) {
                    validateNamespace(file, doc, result, attr, attrValue);
                } else if ("parameterMap".equals(tagName)) {
                    warnDeprecated(file, doc, result, tagName, attr);
                } else if ("parameter".equals(attrName) || "parameterMap".equals(attrName)) {
                    warnDeprecated(file, doc, result, attrName, attr);
                }
            }
        }

        NodeList nodes = element.getChildNodes();
        for (int j = 0; j < nodes.getLength(); j++) {
            Node child = nodes.item(j);
            if (child instanceof IDOMElement) {
                validateElement(project, (IDOMElement) child, file, doc, reporter, result);
            }
        }
    }

    private void validateNamespace(IFile file, IDOMDocument doc, ValidationResult result, IDOMAttr attr,
            String attrValue) {
        if (attrValue == null || attrValue.length() == 0) {
            addMarker(result, file, doc.getStructuredDocument(), attr, NAMESPACE_MANDATORY, IMarker.SEVERITY_ERROR,
                    IMarker.PRIORITY_HIGH, "Namespace must be specified.");
        }
    }

    private void validateResultMapId(IJavaProject project, IFile file, IDOMDocument doc, ValidationResult result,
            IDOMAttr attr, String attrValue, IReporter reporter) {
        if (attrValue.indexOf(',') == -1) {
            validateReference(project, file, doc, result, attr, attrValue, "resultMap", reporter);
        } else {
            String[] resultMapArr = attrValue.split(",");
            for (String resultMapRef : resultMapArr) {
                String ref = resultMapRef.trim();
                if (ref.length() > 0) {
                    validateReference(project, file, doc, result, attr, ref, "resultMap", reporter);
                }
            }
        }
    }

    private void validateSelectId(IJavaProject project, IFile file, IDOMDocument doc, ValidationResult result,
            IDOMAttr attr, String attrValue, IReporter reporter) {
        validateReference(project, file, doc, result, attr, attrValue, "select", reporter);
    }

    private void validateSqlId(IJavaProject project, IFile file, IDOMDocument doc, ValidationResult result,
            IDOMAttr attr, String attrValue, IReporter reporter) {
        validateReference(project, file, doc, result, attr, attrValue, "sql", reporter);
    }

    private void validateReference(IJavaProject project, IFile file, IDOMDocument doc, ValidationResult result,
            IDOMAttr attr, String attrValue, String targetElement, IReporter reporter) {
        try {
            if (attrValue.indexOf('$') > -1)
                return;

            if (attrValue.indexOf('.') == -1) {
                // Internal reference
                String xpath = "count(//" + targetElement + "[@id='" + attrValue + "']) > 0";
                if (!XpathUtil.xpathBool(doc, xpath)) {
                    addMarker(result, file, doc.getStructuredDocument(), attr, MISSING_SQL, IMarker.SEVERITY_ERROR,
                            IMarker.PRIORITY_HIGH, targetElement + " with id='" + attrValue + "' not found.");
                }
            } else {
                // External reference
                int lastDot = attrValue.lastIndexOf('.');
                String namespace = attrValue.substring(0, lastDot);
                IFile mapperFile = MapperNamespaceCache.getInstance().get(project, namespace, reporter);
                if (mapperFile == null) {
                    addMarker(result, file, doc.getStructuredDocument(), attr, MISSING_NAMESPACE,
                            IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH,
                            "Namespace='" + namespace + "' not found.");
                } else {
                    String xpath = "count(//" + targetElement + "[@id='" + attrValue.substring(lastDot + 1)
                            + "']) > 0";
                    if (!isElementExists(mapperFile, xpath)) {
                        addMarker(result, file, doc.getStructuredDocument(), attr, MISSING_SQL,
                                IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH,
                                targetElement + " with id='" + attrValue + "' not found.");
                    }
                }
            }
        } catch (XPathExpressionException e) {
            Activator.log(Status.ERROR, "Error while searching sql with id = " + attrValue, e);
        }
    }

    private void validateStatementId(IDOMElement element, IFile file, IDOMDocument doc, ValidationResult result,
            IJavaProject project, IDOMAttr attr, String attrValue)
            throws JavaModelException, XPathExpressionException {
        if (attrValue == null) {
            return;
        }

        String qualifiedName = MybatipseXmlUtil.getNamespace(doc);
        IType javaMapperType = project.findType(qualifiedName);
        if (javaMapperType == null)
            return;

        for (IMethod method : javaMapperType.getMethods()) {
            if (attrValue.equals(method.getElementName()))
                return;
        }
        addMarker(result, file, doc.getStructuredDocument(), attr, MISSING_STATEMENT_METHOD,
                IMarker.SEVERITY_WARNING, IMarker.PRIORITY_HIGH,
                "Method '" + attrValue + "' not found in mapper interface " + qualifiedName);
    }

    private void validateProperty(IDOMElement element, IFile file, IDOMDocument doc, ValidationResult result,
            IJavaProject project, IDOMAttr attr, String attrValue, IReporter reporter) throws JavaModelException {
        String qualifiedName = MybatipseXmlUtil.findEnclosingType(element);
        if (MybatipseXmlUtil.isDefaultTypeAlias(qualifiedName)) {
            return;
        }
        IType type = project.findType(qualifiedName);
        if (type == null) {
            qualifiedName = TypeAliasCache.getInstance().resolveAlias(project, qualifiedName, reporter);
        } else {
            for (String interfaceName : type.getSuperInterfaceNames()) {
                // quick check
                if ("java.util.Map".equals(interfaceName))
                    return;
            }
        }
        if (qualifiedName == null) {
            // Enclosing type is missing. Valitationg properties is meaningless.
            return;
        }
        Map<String, String> fields = BeanPropertyCache.searchFields(project, qualifiedName, attrValue, true, -1,
                true);
        if (fields.size() == 0) {
            addMarker(result, file, doc.getStructuredDocument(), attr, MISSING_TYPE, IMarker.SEVERITY_ERROR,
                    IMarker.PRIORITY_HIGH, "Property '" + attrValue + "' not found in class " + qualifiedName);
        }
    }

    private void validateJavaType(IJavaProject project, IFile file, IDOMDocument doc, IDOMAttr attr,
            String qualifiedName, ValidationResult result, IReporter reporter) throws JavaModelException {
        if (!MybatipseXmlUtil.isDefaultTypeAlias(qualifiedName)
                // The top level class can have '$' in its name,
                // so replacing '$' with '.' is not correct.
                // But, it *finds* non-existent inner class if '$' is used.
                // TODO: inner class is referenced with preceding '$' by MyBatis,
                // so there should be a validtion if '.' is used.
                && project.findType(qualifiedName.replace('$', '.')) == null
                && TypeAliasCache.getInstance().resolveAlias(project, qualifiedName, reporter) == null) {
            addMarker(result, file, doc.getStructuredDocument(), attr, MISSING_TYPE, IMarker.SEVERITY_ERROR,
                    IMarker.PRIORITY_HIGH, "Class/TypeAlias '" + qualifiedName + "' not found.");
        }
    }

    private void warnDeprecated(IFile file, IDOMDocument doc, ValidationResult result, String tagName,
            IDOMAttr attr) {
        addMarker(result, file, doc.getStructuredDocument(), attr, DEPRECATED, IMarker.SEVERITY_WARNING,
                IMarker.PRIORITY_HIGH, "'" + tagName + "' is deprecated and should not be used.");
    }

    private void addMarker(ValidationResult result, IFile file, IStructuredDocument doc, IDOMAttr attr,
            String problemType, int severity, int priority, String message) {
        int start = attr.getValueRegionStartOffset();
        int length = attr.getValueRegionText().length();
        int lineNo = doc.getLineOfOffset(start);
        try {
            // ValidatorMessage marker = ValidatorMessage.create(message, file);
            // marker.setType(MARKER_ID);
            IMarker marker = file.createMarker(MARKER_ID);
            marker.setAttribute(IMarker.SEVERITY, severity);
            marker.setAttribute(IMarker.PRIORITY, priority);
            marker.setAttribute(IMarker.MESSAGE, message);
            marker.setAttribute(IMarker.LINE_NUMBER, lineNo);
            if (start != 0) {
                marker.setAttribute(IMarker.CHAR_START, start);
                marker.setAttribute(IMarker.CHAR_END, start + length);
            }
            // Adds custom attributes.
            marker.setAttribute("problemType", problemType);
            marker.setAttribute("errorValue", attr.getValue());
            // result.add(marker);
        } catch (CoreException e) {
            Activator.log(Status.ERROR, e.getMessage(), e);
        }
    }

    private boolean isElementExists(IFile file, String xpath) {
        IStructuredModel model = null;
        try {
            model = StructuredModelManager.getModelManager().getModelForRead(file);
            IDOMModel domModel = (IDOMModel) model;
            IDOMDocument domDoc = domModel.getDocument();

            return XpathUtil.xpathBool(domDoc, xpath);
        } catch (Exception e) {
            Activator.log(Status.ERROR, "Error occurred during parsing mapper:" + file.getFullPath(), e);
        } finally {
            if (model != null) {
                model.releaseFromRead();
            }
        }
        return false;
    }
}