com.google.publicalerts.cap.XercesCapExceptionMapper.java Source code

Java tutorial

Introduction

Here is the source code for com.google.publicalerts.cap.XercesCapExceptionMapper.java

Source

/*
 * Copyright (C) 2011 Google Inc.
 *
 * 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.google.publicalerts.cap;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.publicalerts.cap.CapException.ReasonType;

import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Attempts to map {@link Reason}s of type {@link ReasonType#OTHER} derived
 * from Xerces-generated {@code SAXParseException}s to reasons with more
 * descriptive {@link ReasonType}s and error messages specific to CAP.
 *
 * <p>Implementation note: Translation is done by parsing the messages
 * generated by the Xerces schema validator. Other validators are not
 * guaranteed to produce the same error messages. The goal is to push as
 * much of the validation as possible out of code and into the .xsd.
 *
 * @author shakusa@google.com (Steve Hakusa)
 */
public class XercesCapExceptionMapper {
    private static final Pattern XERCES_ONE_PARAM_PATTERN = Pattern.compile(".* '(.*)' .*");
    private static final Pattern XERCES_TWO_PARAM_PATTERN = Pattern.compile(".* '(.*)'.* '(.*)'.*");
    private static final Pattern ANON_TYPE_PATTERN = Pattern.compile(".*AnonType_(.*)alert.*");

    private static final Set<String> VALID_TAGS = buildValidTagSet();
    private static final Set<String> DATE_TAGS = ImmutableSet.of("sent", "effective", "onset", "expires");

    static String stripTagNamespace(String tag) {
        int index = tag.indexOf(':');

        if (index < 0) {
            // The tag does not have a namespace specified
            return tag;
        }

        return tag.substring(index + 1, tag.length());
    }

    /**
     * @see #map(Reasons)
     *
     * @param exception the exception with reasons to map
     * @return a new exception with mapped reasons
     */
    public CapException map(CapException exception) {
        return new CapException(map(exception.getReasons()));
    }

    /**
     * Attempts to map {@link Reason}s of type {@link ReasonType#OTHER} derived
     * from Xerces-generated {@code SAXParseException}s to reasons with more
     * descriptive {@link ReasonType}s and error messages specific to CAP.
     *
     * <p>Each {@link Reason} is expected to have 3 message parameters;
     * the first is the {@code SAXParseException} error message;
     * the second is the localName of the element being parsed at the time of
     * the exception;
     * the third is any characters that had been parsed at the time of the
     * exception.
     *
     * @param reasons the reasons to map
     * @return the mapped reasons
     */
    public Reasons map(Reasons reasons) {
        Set<Reason> reasonsSet = Sets.newHashSet();

        for (Reason r : reasons) {
            if (r.getType() == ReasonType.OTHER) {
                r = map(r);
            }
            if (r != null && !reasonsSet.contains(r)) {
                reasonsSet.add(r);
            }
        }

        return Reasons.newBuilder().addAll(reasonsSet).build();
    }

    private Reason map(Reason reason) {
        String message = reason.getMessage();
        String code = extractMessageCode(message);
        String tag = (String) reason.getMessageParam(1);
        String value = (String) reason.getMessageParam(2);

        ReasonType type = ReasonType.OTHER;
        String xPath = reason.getXPath();
        String[] args = new String[] { reason.getMessage() };

        // Codes defined at http://www.w3.org/TR/xmlschema-1/#validation_failures
        if ("cvc-complex-type.2.4.a".equals(code)) {
            Matcher matcher = XERCES_TWO_PARAM_PATTERN.matcher(message);
            if (matcher.matches()) {
                if (VALID_TAGS.contains(stripTagNamespace(matcher.group(1)))) {
                    if (matcher.group(1).equals(tag)) {
                        type = ReasonType.DUPLICATE_ELEMENT;
                        args = new String[] { tag, value };
                        xPath += '/' + stripTagNamespace(tag) + "[2]";
                        //TODO(sschiavoni): count actual occurrences
                    } else {
                        type = ReasonType.INVALID_SEQUENCE;
                        args = new String[] { matcher.group(2), matcher.group(1) };
                        xPath += '/' + stripTagNamespace(matcher.group(1)) + "[1]";
                    }
                } else {
                    type = ReasonType.UNSUPPORTED_ELEMENT;
                    args = new String[] { matcher.group(1) };
                    xPath += '/' + stripTagNamespace(matcher.group(1)) + "[1]";
                }
            }
        } else if ("cvc-complex-type.2.4.b".equals(code)) {
            // Missing required element
            Matcher matcher = XERCES_TWO_PARAM_PATTERN.matcher(message);
            if (matcher.matches()) {
                type = ReasonType.MISSING_REQUIRED_ELEMENT;
                args = new String[] { matcher.group(1), matcher.group(2) };
            }
        } else if ("cvc-complex-type.2.3".equals(code)) {
            // Invalid character data in an element
            Matcher matcher = XERCES_ONE_PARAM_PATTERN.matcher(message);
            if (matcher.matches()) {
                type = ReasonType.INVALID_CHARACTERS;
                args = new String[] { matcher.group(1) };
            }
        } else if ("cvc-datatype-valid.1.2.1".equals(code)) {
            // invalid datetime or integer
            if (DATE_TAGS.contains(tag)) {
                type = ReasonType.INVALID_DATE;
                args = new String[] { tag, value };
            }
        } else if ("cvc-enumeration-valid".equals(code)) {
            // invalid enum
            Matcher matcher = XERCES_TWO_PARAM_PATTERN.matcher(message);
            if (matcher.matches()) {
                type = ReasonType.INVALID_ENUM_VALUE;
                args = new String[] { tag, value, matcher.group(2) };
            }
        } else if ("cvc-pattern-valid".equals(code)) {
            // fail regex pattern match
            Map<String, ReasonType> tagReasonTypeMap = ImmutableMap.of("identifier", ReasonType.INVALID_IDENTIFIER,
                    "sender", ReasonType.INVALID_SENDER, "references", ReasonType.INVALID_REFERENCES, "circle",
                    ReasonType.INVALID_CIRCLE);

            if (tagReasonTypeMap.containsKey(tag)) {
                type = tagReasonTypeMap.get(tag);
                args = new String[] { value };
            } else if ("polygon".equals(tag)) {
                type = ReasonType.INVALID_POLYGON;
                args = new String[] { value.length() > 50 ? value.substring(0, 47) + "..." : value };
            } else if (DATE_TAGS.contains(tag)) {
                type = ReasonType.INVALID_DATE;
                args = new String[] { tag, value };
            } else {
                Matcher matcher = ANON_TYPE_PATTERN.matcher(message);
                if (matcher.matches()) {
                    tag = matcher.group(1);
                }
                type = tagReasonTypeMap.containsKey(tag) ? tagReasonTypeMap.get(tag) : ReasonType.INVALID_VALUE;
                args = new String[] { tag, value };
            }
        } else if ("cvc-type.3.1.2".equals(code)) {
            // Invalid character data in an element
            type = ReasonType.INVALID_VALUE;
            args = new String[] { tag, value };
        } else if ("cvc-type.3.1.3".equals(code)) {
            // Any time this is generated, it is coupled with one of the handled
            // cases above, so do not add a duplicate here
            return null;
        }

        return new Reason(xPath, type, (Object[]) args);
    }

    private String extractMessageCode(String message) {
        if (message == null || message.indexOf(':') < 0) {
            return null;
        }
        return message.substring(0, message.indexOf(':'));
    }

    private static Set<String> buildValidTagSet() {
        ImmutableSet.Builder<String> ret = ImmutableSet.builder();
        ret.add("alert");
        for (Descriptor d : Cap.getDescriptor().getMessageTypes()) {
            for (FieldDescriptor fd : d.getFields()) {
                ret.add(CapUtil.javaCase(fd.getName()));
            }
        }
        return ret.build();
    }
}