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

rapture.common.RaptureURI Maven / Gradle / Ivy

/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2011-2016 Incapture Technologies LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package rapture.common;

import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import rapture.common.exception.RaptureExceptionFactory;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;

@JsonSerialize(using = RaptureURIJacksonSerializer.class)
@JsonDeserialize(using = RaptureURIJasksonDeserializer.class)
public class RaptureURI implements Cloneable {

    private Scheme scheme;
    private String authority;
    private String docPath;
    private String version;
    private String asOfTime;
    private String attribute;
    private String element;

    @SuppressWarnings("unused")
    private static final Logger log = Logger.getLogger(RaptureURI.class);

    private RaptureURI() {
    }

    public RaptureURI(String documentURI) {
        this(documentURI, null);
    }

    public RaptureURI(String documentURI, Scheme defaultScheme) {
        if ("//".equals(documentURI) || "/".equals(documentURI) || "".equals(documentURI) || documentURI == null) {
            this.scheme = defaultScheme;
            this.authority="";
        } else try {
            Parser parser = new Parser(documentURI, defaultScheme);
            parser.parse();
            this.scheme = parser.getScheme();
            this.authority = parser.getAuthority();
            this.docPath = parser.getDocPath();
            this.version = parser.getVersion();
            this.asOfTime = parser.getAsOfTime();
            this.element = parser.getElement();
            this.attribute = parser.getAttribute();
        } catch (URISyntaxException e) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_INTERNAL_ERROR,
                    String.format("Unable to parse URI %s: %s", documentURI, e.getMessage()), e);
        }

    }

    // getDocPath should return the Doc Path. If we need helper methods, let's
    // add them.

    public String getDocPath() {
        return docPath == null ? "" : docPath;
    }

    public String getDocPathWithAttributes() {
        return attribute == null ? getDocPath() : getDocPath() + Parser.SEPARATOR_CHAR + Parser.ATTRIBUTE_CHAR + attribute;
    }

    public String getDocPathWithElement() {
        return element == null ? getDocPath() : getDocPath() + Parser.ELEMENT_CHAR + element;
    }

    public String getShortPath() {
        StringBuilder sb = new StringBuilder();
        sb.append(getAuthority()).append(Parser.SEPARATOR_CHAR).append(getDocPath());
        return sb.toString();
    }

    public String getFullPath() {
        StringBuilder sb = new StringBuilder();
        sb.append(getShortPath());
        if (version != null) sb.append(Parser.VERSION_CHAR).append(version);
        if (asOfTime != null) sb.append(Parser.VERSION_CHAR).append(asOfTime);
        if (attribute != null) sb.append(Parser.SEPARATOR_CHAR).append(Parser.ATTRIBUTE_CHAR).append(attribute);
        if (element != null) sb.append(Parser.ELEMENT_CHAR).append(element);
        return sb.toString();
    }

    public String getParent() {
        StringBuilder sb = new StringBuilder();
        sb.append(scheme).append("://");
        if (docPath != null) {
            sb.append(getAuthority());
            if (docPath.contains("/")) {
                sb.append(Parser.SEPARATOR_CHAR).append(docPath.substring(0, docPath.lastIndexOf('/')));
            }
        }
        return sb.toString();
    }
    
    public RaptureURI getParentURI() {
        if (this.hasDocPath() == false) return null;
        RaptureURI ret = null;
        try {
            ret = (RaptureURI) clone();
        } catch (CloneNotSupportedException e) {
        	// it is.
        }
        if (ret.docPath.charAt(ret.docPath.length()-1) == Parser.SEPARATOR_CHAR) {
            ret.docPath.substring(0, ret.docPath.length()-1);
        }
        int lastIndex = ret.docPath.lastIndexOf(Parser.SEPARATOR_CHAR);
        ret.docPath = (lastIndex > 0) ? ret.docPath.substring(0,lastIndex) : "";
        return ret;
    }

    // For scheme://auth/path/leaf return leaf
    public String getLeafName() {
        if (hasDocPath() == false) return null;
        int lastIndex = docPath.lastIndexOf(Parser.SEPARATOR_CHAR);
        return (lastIndex > 0) ? docPath.substring(lastIndex+1) : docPath;
    }

    public Scheme getScheme() {
        return scheme;
    }

    public String getAuthority() {
        return authority;
    }

    public String getVersion() {
        return version;
    }

    public String getAsOfTime() {
        return asOfTime;
    }

    public String getAttribute() {
        return attribute;
    }

    public String getAttributeName() {
        if (attribute != null) {
            return attribute.split(Parser.SEPARATOR_CHAR.toString())[0];
        } else {
            return null;
        }
    }

    public String getAttributeKey() {
        if (attribute != null) {
            String[] retVal = attribute.split(Parser.SEPARATOR_CHAR.toString(), 2);
            return (retVal.length > 1) ? retVal[1] : null;
        } else {
            return null;
        }
    }

    public String getElement() {
        return element;
    }

    @Override
    public String toString() {
        return prependScheme(getFullPath());
    }

    public String toShortString() {
        return prependScheme(getShortPath());
    }

    public String toAuthString() {
        return prependScheme(getAuthority());
    }

    private String prependScheme(String uri) {
        StringBuilder sb = new StringBuilder();
        if (scheme != null) sb.append(scheme).append(":");
        sb.append(uri.startsWith("/") ? "/" : "//");
        sb.append(uri);
        return sb.toString();
    }

    public boolean hasDocPath() {
        return docPath != null && docPath.length() > 0;
    }

    public boolean hasAttribute() {
        return attribute != null;
    }

    public boolean hasElement() {
        return element != null;
    }

    public boolean hasScheme() {
        return scheme != null;
    }

    public boolean hasVersion() {
        return version != null;
    }

    public boolean hasAsOfTime() {
        return asOfTime != null;
    }

    public String getDisplayName() {
        return Strings.isNullOrEmpty(docPath) ? "" : docPath;
    }

    public static Builder builder(Scheme scheme, String authority) {
        return new Builder(scheme, authority);
    }

    public static Builder builder(RaptureURI uri) {
        return new Builder(uri);
    }

    public RaptureURI withoutAttribute() {
        return RaptureURI.builder(this).attribute(null).build();
    }

    public RaptureURI withoutDecoration() {
        return RaptureURI.builder(this).attribute(null).element(null).version(null).asOfTime(null).build();
    }

    public static class Builder {
        private final RaptureURI result;

        public Builder(Scheme scheme, String authority) {
            if (authority == null) {
                throw new IllegalArgumentException("Authority cannot be null");
            }
            result = new RaptureURI();
            result.scheme = scheme;
            result.authority = authority;
        }

        public Builder(RaptureURI uri) {
            result = new RaptureURI();
            result.scheme = uri.scheme;
            result.authority = uri.authority;
            result.docPath = uri.docPath;
            result.version = uri.version;
            result.asOfTime = uri.asOfTime;
            result.attribute = uri.attribute;
            result.element = uri.element;
        }

        public Builder docPath(String docPath) {
        	if (!StringUtils.isEmpty(docPath)) {
        		while (docPath.endsWith("/")) docPath = docPath.substring(0, docPath.length()-1);
        	}
        	result.docPath = docPath;
            return this;
        }

        public Builder attribute(String attribute) {
            result.attribute = attribute;
            return this;
        }

        public Builder version(String version) {
            result.version = version;
            return this;
        }

        public Builder asOfTime(String asOfTime) {
            result.asOfTime = asOfTime;
            return this;
        }

        public Builder element(String element) {
            result.element = element;
            return this;
        }

        public RaptureURI build() {
            return result;
        }

        public String asString() {
            return build().toString();
        }
    }

    public static RaptureURI createFromFullPath(String fullPath, Scheme scheme) {
        int sliceCount = 2;
        String[] parts = fullPath.split(Parser.SEPARATOR_CHAR.toString(), sliceCount);
        Preconditions.checkArgument(parts.length == sliceCount, "Not a valid path: " + fullPath);
        return RaptureURI.builder(scheme, parts[0]).docPath(parts[1]).build();
    }

    public static RaptureURI createFromFullPathWithAttribute(String fullPath, String attribute, Scheme scheme) {
        int sliceCount = 2;
        String[] parts = fullPath.split(Parser.SEPARATOR_CHAR.toString(), sliceCount);
        Preconditions.checkArgument(parts.length == sliceCount, "Not a valid path: " + fullPath);
        String docPath = parts[1];
        // do not allow the empty string
        if (StringUtils.isBlank(docPath)) {
            docPath = null;
        }
        return RaptureURI.builder(scheme, parts[0]).docPath(docPath).attribute(attribute).build();
    }

    public int getDocPathDepth() {
        int count = 1;
        if (!Strings.isNullOrEmpty(docPath)) {
            count++;
            for (int i = 0; i < docPath.length(); i++) {
                if (docPath.charAt(i) == Parser.SEPARATOR) {
                    count++;
                }
            }
        }
        return count;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((attribute == null) ? 0 : attribute.hashCode());
        result = prime * result + ((docPath == null) ? 0 : docPath.hashCode());
        result = prime * result + ((element == null) ? 0 : element.hashCode());
        result = prime * result + ((authority == null) ? 0 : authority.hashCode());
        result = prime * result + ((scheme == null) ? 0 : scheme.hashCode());
        result = prime * result + ((version == null) ? 0 : version.hashCode());
        result = prime * result + ((asOfTime == null) ? 0 : asOfTime.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        RaptureURI other = (RaptureURI) obj;
        if (attribute == null) {
            if (other.attribute != null) return false;
        } else if (!attribute.equals(other.attribute)) return false;
        if (docPath == null) {
            if (other.docPath != null) return false;
        } else if (!docPath.equals(other.docPath)) return false;
        if (element == null) {
            if (other.element != null) return false;
        } else if (!element.equals(other.element)) return false;
        if (authority == null) {
            if (other.authority != null) return false;
        } else if (!authority.equals(other.authority)) return false;
        if (scheme != other.scheme) return false;
        if (version == null) {
            if (other.version != null) return false;
        } else if (!version.equals(other.version)) return false;
        if (asOfTime == null) {
            if (other.asOfTime != null) return false;
        } else if (!asOfTime.equals(other.asOfTime)) return false;
        return true;
    }

    public static final class Parser {

        public static boolean isValidDocPathChar(int ch) {
            return Character.isUnicodeIdentifierPart(ch) || DOC_PATH_REPEAT.contains(ch);
        }

        public static final Character VERSION_CHAR = '@';
        public static final Character ELEMENT_CHAR = '#';
        public static final Character ATTRIBUTE_CHAR = '$';
        public static final Character SEPARATOR_CHAR = '/';
        public static final Character COLON_CHAR = ':';

        static final Integer VERSION = new Integer(VERSION_CHAR);
        static final Integer ELEMENT = new Integer(ELEMENT_CHAR);
        static final Integer ATTRIBUTE = new Integer(ATTRIBUTE_CHAR);
        static final Integer SEPARATOR = new Integer(SEPARATOR_CHAR);
        static final Integer COLON = new Integer(COLON_CHAR);

        private static final Set OPTIONAL_CONTROL_CHARS = ImmutableSet. of(Parser.ATTRIBUTE, Parser.VERSION, Parser.ELEMENT);

/**
         * These are the 'special' characters that we accept. Loosely based on unreserved, as defined in RFC-2396 (Appendix A)
         * Note that we do not currently allow space or plus
         * 
         * reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
         * unreserved  = alphanum | mark
         * mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
         * delims      = "<" | ">" | "#" | "%" | <">
         * unwise      = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
         *
         * Given that many of the encoders rely on RFC-2396 based algorithms - eg java.net.URLEncoder - we should certainly allow 
         * all of unreserved - In fact thete's an argument to be made for allowing anything that's not explicitly defined as a Character above:
         *      VERSION_CHAR, ELEMENT_CHAR, ATTRIBUTE_CHAR, SEPARATOR_CHAR, COLON_CHAR
         */

        private static final Set WHITELIST = ImmutableSet.of(new Integer('-'), new Integer('.'), new Integer('_'), new Integer('~'),
                new Integer('!'), new Integer('*'), new Integer('"'), new Integer('('), new Integer(')'), new Integer('|'), new Integer('%'));

        /**
         * Contains any characters that we allow for authority
         */
        private static final Set AUTHORITY;

        static {
            com.google.common.collect.ImmutableSet.Builder builder = ImmutableSet. builder();
            builder.add(Parser.VERSION).add(COLON).addAll(WHITELIST);
            AUTHORITY = builder.build();
        }

        private boolean isValidAuthorityChar(int ch) {
            return Character.isUnicodeIdentifierPart(ch) || AUTHORITY.contains(ch);
        }

        /**
         * Contains any characters we allow for the doc path, which can be repeated as many times as necessary
         */
        private static final Set DOC_PATH_REPEAT;

        static {
            com.google.common.collect.ImmutableSet.Builder builder = ImmutableSet. builder();
            builder.addAll(WHITELIST).add(Parser.SEPARATOR).add(COLON).addAll(OPTIONAL_CONTROL_CHARS);
            DOC_PATH_REPEAT = builder.build();
        }

        /**
         * We populate a map, mapping string representations of {@link Scheme} to their object equivalents. This allows faster conversion of text to string,
         * rather than doing {@link Scheme#valueOf(String)}
         */
        private static final Map valToScheme;

        static {
            com.google.common.collect.ImmutableMap.Builder builder = ImmutableMap. builder();
            for (Scheme scheme : Scheme.values()) {
                builder.put(scheme.toString(), scheme);
            }
            valToScheme = builder.build();
        }

        private int i;

        private int inputLength;
        private String input;
        private int[] chars; // Got to support Un

        Map badChars;

        private Scheme defaultScheme;

        Set alreadySeen;

        private Scheme scheme;

        private String docPath;

        private String attribute;

        private String element;

        private String version;

        private String asOfTime;

        private String authority;

        public Parser(String input, Scheme defaultScheme) {
            this.input = input;
            this.defaultScheme = defaultScheme;
            alreadySeen = new HashSet();
            badChars = new HashMap();
            inputLength = input.length();
            chars = new int[inputLength];
        }

        /**
         * Attempt to parse an input {@link String}. This method should be used to parse the input and then retrieve the resulting parts using methods like
         * {@link #getAuthority()}, {@link #getScheme()}, etc. {@link Parser} is a stateful object. For example:
         * 

* *

         * URIParser parser = new URIParser(input, defaultScheme);
         * parser.parse();
         * this.scheme = parser.getScheme();
         * this.authority = parser.getAuthority();
         * 
* * @return A {@link RaptureURI} for this string * @throws URISyntaxException * If parsing fails due to bad syntax in the input */ void parse() throws URISyntaxException { if (inputLength < 3) { throw new URISyntaxException(input, "Invalid URI, length must be at least 3"); } for (int counter = 0; counter < inputLength; counter++) chars[counter] = input.codePointAt(counter); i = 0; // parse scheme int endSchemeIndex = input.indexOf("://"); if (endSchemeIndex == -1) { if (defaultScheme != null) { scheme = defaultScheme; } else { throw new URISyntaxException(input, "URI does not contain a scheme and no default scheme is specified"); } if (chars[0] == Parser.SEPARATOR && chars[1] == Parser.SEPARATOR) i += 2; } else { String schemeString = new String(chars, 0, endSchemeIndex); i = endSchemeIndex + 3; scheme = valToScheme.get(schemeString); if (scheme == null) { throw new URISyntaxException(input, "Invalid scheme " + schemeString); } } // You can't have a null authority and a doc path: foo:////bar/baz --> foo://bar/baz while ((i < inputLength) && (chars[i] == '/')) i++; // skip over slashes // parse authority boolean isInvalidAuthority = false; int authorityStartIndex = i; while (i < inputLength) { int currChar = chars[i]; if (currChar == Parser.SEPARATOR) { break; } else { if (!isValidAuthorityChar(currChar)) { badChars.put(i, currChar); isInvalidAuthority = true; } i++; } } authority = new String(chars, authorityStartIndex, i - authorityStartIndex); if (isInvalidAuthority) { throw new URISyntaxException(input, String.format("Invalid authority '%s': bad characters are %s", authority, badCharsToString())); } boolean isInvalidDocPathChar = false; do { i++; // skip over slashes if (i >= inputLength) { return; } } while (chars[i] == '/'); int docPathStartIndex = i; while (i < inputLength && !OPTIONAL_CONTROL_CHARS.contains(chars[i])) { if (OPTIONAL_CONTROL_CHARS.contains(chars[i])) { break; } else { if (!isValidDocPathChar(chars[i])) { isInvalidDocPathChar = true; badChars.put(i, chars[i]); } i++; } } if (i > docPathStartIndex && chars[i - 1] == Parser.SEPARATOR) { docPath = new String(chars, docPathStartIndex, i - 1 - docPathStartIndex); } else { docPath = new String(chars, docPathStartIndex, i - docPathStartIndex); } docPath = docPath.replaceAll("//+", "/"); if (isInvalidDocPathChar) { throw new URISyntaxException(input, String.format("Invalid doc path '%s': bad characters are %s ", docPath, badCharsToString())); } readOptionalPart(); readOptionalPart(); readOptionalPart(); } private String badCharsToString() { StringBuilder sb = new StringBuilder(); for (Entry entry : badChars.entrySet()) { sb.append(Character.toChars(entry.getValue())).append(" (pos ").append(entry.getKey()).append("); "); } return sb.toString(); } private void readOptionalPart() throws URISyntaxException { if (i < inputLength) { int controlChar = chars[i]; alreadySeen.add(controlChar); i++; int startIndex = i; while (i < inputLength) { int currChar = chars[i]; if (OPTIONAL_CONTROL_CHARS.contains(currChar)) { if (alreadySeen.contains(currChar)) { throw new URISyntaxException(input, String.format("Error, '%s' cannot appear twice because it implies redefining the same entity", currChar), i); } else { break; } } else { i++; } } int end = i - startIndex; // remove trailing separators while (chars[end + startIndex - 1] == Parser.SEPARATOR) { end--; } String text = new String(chars, startIndex, end); if (text.length() > 0) { if (controlChar == VERSION) { if (text.matches("\\d+")) { version = text; } else { asOfTime = text; } } else if (controlChar == ELEMENT) { element = text; } else if (controlChar == ATTRIBUTE) { attribute = text; } } } } public Scheme getScheme() { return scheme; } public String getDocPath() { return docPath; } public String getAttribute() { return attribute; } public String getElement() { return element; } public String getVersion() { return version; } public String getAsOfTime() { return asOfTime; } public String getAuthority() { return authority; } } public String debug() { StringBuilder sb = new StringBuilder(); sb.append(" Scheme: ").append(scheme); sb.append(" Authority: ").append(authority); sb.append(" Doc Path: ").append(docPath).append(" Depth: ").append(this.getDocPathDepth()); sb.append(" Version: ").append(version); sb.append(" As Of Time: ").append(asOfTime); sb.append(" Attribute: ").append(attribute); sb.append(" Element: ").append(element).append("\n"); sb.append(" Attribute Key-Name: ").append(this.getAttributeKey()).append("-").append(this.getAttributeName()).append("\n"); return sb.toString(); } public RaptureURI withoutElement() { return builder(this).element(null).build(); } public static RaptureURI newScheme(String uri, Scheme newScheme) { RaptureURI retVal = new RaptureURI(uri); retVal.scheme = newScheme; return retVal; } public static RaptureURI newScheme(RaptureURI uri, Scheme newScheme) { RaptureURI retVal = null; try { retVal = (RaptureURI) uri.clone(); retVal.scheme = newScheme; } catch (CloneNotSupportedException e) { log.info("This can't happen", e); } return retVal; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy