com.tesora.dve.common.PEUrl.java Source code

Java tutorial

Introduction

Here is the source code for com.tesora.dve.common.PEUrl.java

Source

package com.tesora.dve.common;

/*
 * #%L
 * Tesora Inc.
 * Database Virtualization Engine
 * %%
 * Copyright (C) 2011 - 2014 Tesora Inc.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;

import com.tesora.dve.exceptions.PEException;

public class PEUrl {

    private static String URL_PARAMETER_SEPARATOR = "?";

    public static String stripUrlParameters(final String url) {
        return StringUtils.substringBeforeLast(url.trim(), URL_PARAMETER_SEPARATOR);
    }

    private String protocol;
    private String subProtocol;
    private String host;
    private int port;
    private String path;
    private String query;
    private Properties queryOptions = new Properties();
    private boolean hasPort = false;

    /**
     * Create an uninitialized instance of PEUrl.
     */
    public PEUrl() {
    }

    /**
     * Create an instance of PEUrl and initialize with values from Properties
     * object. If the Properties contains a prefix."url" key, PEUrl will be
     * initialized with that; otherwise it will look for prefix."host",
     * prefix."port", prefix."type" and prefix."dbname"
     * 
     * This doesn't currently handle passing query options via a properties file
     * 
     * @param Properties
     *            props
     * @param String
     *            prefix
     * @throws PEException
     *             if the ".url" is malformed or the props doesn't contain
     *             enough info to make a valid URL.
     */
    public PEUrl(Properties props, String prefix) throws PEException {
        this(props, prefix, false);
    }

    /**
     * Create an instance of PEUrl and initialize with values from Properties
     * object. If the Properties contains a prefix."url" key, PEUrl will be
     * initialized with that; otherwise it will look for prefix."host",
     * prefix."port", prefix."type" and prefix."dbname". If setDefaults is true,
     * then defaults will be filled in for host (localhost), port (3306) and
     * type (mysql).
     * 
     * This doesn't currently handle passing query options via a properties file
     * 
     * @param Properties
     *            props
     * @param String
     *            prefix
     * @param boolean useDefaults
     * @throws PEException
     *             if the ".url" is malformed.
     */

    public PEUrl(Properties props, String prefix, boolean useDefaults) throws PEException {
        initializeFromProps(props, prefix, useDefaults);

        if (!isInitialized()) {
            throw new PEException("Error creating URL object from Properties: " + props.toString());
        }

    }

    /**
     * Initialize with the information contained in <code>urlString</code>
     * 
     * @param urlString
     *            - a String representing a valid URL
     * @throws PEException
     *             - if <code>urlString</code> is not a properly formed URL
     */
    public static PEUrl fromUrlString(String urlString) throws PEException {
        PEUrl url = new PEUrl();

        url.parseURL(urlString);

        if (!url.isInitialized())
            throw new PEException("Error creating URL object from string: " + urlString);

        return url;
    }

    public static PEUrl isValidUrlWithPort(String urlString) throws PEException {

        // This is a test to ensure the url is well formed
        PEUrl pUrl = fromUrlString(urlString);

        // PEUrl will have done some checking - but we want to ensure we
        // have a valid port
        if (!pUrl.hasPort())
            throw new PEException("URL '" + urlString + "' does not contain a valid port");

        return pUrl;
    }

    /**
     * Initialize with the information contained in <code>connectString</code>
     * 
     * @param connectString
     *            - a String representing parameters for a URL in
     *            "connect string" format. e.g.
     *            "host=localhost;port=3306;dbname=dve_catalog"
     * @throws PEException
     *             - if <code>connectString</code> is malformed - connectString
     *             must contain at least a "host" token
     * 
     */
    public static PEUrl fromConnectString(String connectString) throws PEException {
        PEUrl url = new PEUrl();

        // TODO this "parser" will not handle embedded delimeters
        String delims = "[=;]";
        String[] tokens = connectString.split(delims);

        if (tokens.length % 2 != 0)
            throw new PEException("Connect String does not contain the correct number of elements");

        Properties props = new Properties();
        for (int i = 0; i < tokens.length; i = i + 2) {
            props.setProperty(tokens[i], tokens[i + 1]);
        }

        url.initializeFromProps(props, null /* prefix */, false /* useDefaults */);

        if (!url.isInitialized())
            throw new PEException("Error creating URL object from connect string: " + connectString);

        return url;
    }

    /**
     * Is this PEUrl instance properly initialized. Minimally, this means that
     * the protocol, subProtocol and host are set. (set via PEUrl(urlString)
     * constructor or calling setXXX methods
     * 
     * @return boolean indicating if the instance is properly initialized
     */
    boolean isInitialized() {
        return (protocol != null && subProtocol != null && host != null && !host.isEmpty());
    }

    /**
     * Will populate an empty PEUrl instance with the default URL parameters for
     * a MySQL JDBC connection. Calling example:
     * <code>PEUrl url = new PEUrl().createMysqlDefaultURL()</code>
     * 
     * @return PEUrl instance representing "jdbc:mysql://localhost:3306"
     */
    public PEUrl createMysqlDefaultURL() {
        setProtocol(PEConstants.MYSQL_PROTOCOL);
        setSubProtocol(PEConstants.MYSQL_SUBPROTOCOL);
        setHost(PEConstants.MYSQL_HOST);
        setPort(PEConstants.MYSQL_PORT);
        return this;
    }

    /**
     * Gets a queryOption from the option properties based on a key value.
     * 
     * @param optionKey
     *            - the key for the option to return
     * @return String - value of option for key <code>optionKey</code>
     */
    public String getOption(String optionKey) {
        return queryOptions.getProperty(optionKey);
    }

    /**
     * Returns a string representation of the (i.e. a URL) of the current state
     * of this PEUrl instance. Instance must be properly initialized - meaning
     * that at least the protocol, subProtocol and host attributes must be set.
     * 
     * @return String representation of URL
     * @throws PEException
     *             - if instance isn't properly initialized.
     */
    public String getURL() throws PEException {
        if (!isInitialized())
            throw new PEException("Cannot call getURL method when PEUrl object is not initialized");

        StringBuffer sb = new StringBuffer();

        sb.append(protocol).append(':').append(subProtocol).append("://").append(getAuthority());

        if (path != null) {
            if (!path.isEmpty() && !path.startsWith("/")) {
                sb.append('/');
            }
            sb.append(path);
        }

        if (query != null)
            sb.append('?').append(query);

        return sb.toString();
    }

    public String getProtocol() {
        return protocol;
    }

    /**
     * Set the protocol to <code>protocol</code>
     * 
     * @param protocol
     * @return PEUrl this
     */
    public PEUrl setProtocol(String protocol) {
        this.protocol = protocol;
        return this;
    }

    public String getSubProtocol() {
        return subProtocol;
    }

    /**
     * Set the sub protocol to <code>subProtocol</code>
     * 
     * @param subProtocol
     * @return PEUrl this
     */
    public PEUrl setSubProtocol(String subProtocol) {
        this.subProtocol = subProtocol;
        return this;
    }

    public String getAuthority() {
        return host + (hasPort ? ":" + port : "");
    }

    /**
     * Set the authority to <code>authority</code> This set the host and port as
     * appropriate. e.g. calling <code>setAuthority("localhost:6800")</code>
     * will set host to "localhost" and port to 6800
     * 
     * @param authority
     * @return PEUrl this
     */
    public void setAuthority(String authority) {
        String[] authorityA = authority.split(":");
        if (authorityA.length == 2) {
            hasPort = true;
            port = Integer.parseInt(authorityA[1]);
        }

        host = authorityA[0];
    }

    public String getHost() {
        return host;
    }

    /**
     * Set the host to <code>host</code>.
     * 
     * @param host
     * @return PEUrl this
     */
    public PEUrl setHost(String host) {
        this.host = host;
        return this;
    }

    public int getPort() {
        return port;
    }

    /**
     * Set the port to <code>port</code>.
     * 
     * @param int port
     * @return PEUrl this
     */
    public PEUrl setPort(int port) {
        hasPort = true;
        this.port = port;
        return this;
    }

    public boolean hasPort() {
        return hasPort;
    }

    /**
     * Set the port to <code>port</code>.
     * 
     * @param String
     *            port
     * @return PEUrl this
     */
    public PEUrl setPort(String port) {
        setPort(Integer.parseInt(port));
        return this;
    }

    public String getPath() {
        return path;
    }

    /**
     * Set the path to <code>path</code>.
     * 
     * @param String
     *            path
     * @return PEUrl this
     */
    public PEUrl setPath(String path) {
        this.path = path;
        return this;
    }

    public String getQuery() {
        return query;
    }

    /**
     * Set the query options to <code>query</code>. Query options are of the
     * form "opt1Key=optValue1&opt2Key=optValue2"
     * 
     * @param String
     *            query
     * @return PEUrl this
     */
    public PEUrl setQuery(String query) throws PEException {
        this.query = query;
        queryOptions = parseURLQuery(query);
        return this;
    }

    public PEUrl clearQuery() {
        this.query = null;
        queryOptions.clear();
        return this;
    }

    /**
     * Return all the query options as <code>Properties</code>
     * 
     * @return Properties - the query options
     */
    public Properties getQueryOptions() {
        return queryOptions;
    }

    /**
     * Set one query option for the URL.
     * 
     * @param key
     *            for Option
     * @param value
     *            for Option
     * @return PEUrl this
     */
    public PEUrl setQueryOption(String key, String value) {
        queryOptions.setProperty(key, value);
        calcQueryString();
        return this;
    }

    public PEUrl setQueryOptions(Properties props) {
        for (String key : props.stringPropertyNames()) {
            setQueryOption(key, props.getProperty(key));
        }

        return this;
    }

    private void initializeFromProps(Properties props, String prefix, boolean useDefaults) throws PEException {
        // TODO handle query options

        String normPrefix = PEStringUtils.normalizePrefix(prefix);

        if (props.getProperty(normPrefix + "url") != null) {
            parseURL(props.getProperty(normPrefix + "url"));
        } else {
            if (useDefaults)
                createMysqlDefaultURL();
            else {
                setProtocol(PEConstants.MYSQL_PROTOCOL);
                setSubProtocol(PEConstants.MYSQL_SUBPROTOCOL);
            }
            if (props.getProperty(normPrefix + "host") != null)
                setHost(props.getProperty(normPrefix + "host"));
            if (props.getProperty(normPrefix + "port") != null)
                setPort(props.getProperty(normPrefix + "port"));
            if (props.getProperty(normPrefix + "dbname") != null)
                setPath(props.getProperty(normPrefix + "dbname"));
        }
    }

    // [protocol:subprotocol:][//authority][path][?query][#fragment]
    private PEUrl parseURL(String urlString) throws PEException {

        try {
            URI parser = new URI(urlString.trim());
            final String protocol = parser.getScheme();

            parser = URI.create(parser.getSchemeSpecificPart());
            final String subProtocol = parser.getScheme();
            final String authority = parser.getAuthority();

            if ((protocol == null) || (subProtocol == null)) {
                throw new PEException("Malformed URL '" + urlString + "' - incomplete protocol");
            } else if (authority == null) {
                throw new PEException("Malformed URL '" + urlString + "' - invalid authority");
            }

            this.setProtocol(protocol);
            this.setSubProtocol(subProtocol);

            final String host = parser.getHost();
            if (host != null) {
                this.setHost(host);
            }

            final int port = parser.getPort();
            if (port != -1) {
                this.setPort(port);
            }

            final String query = parser.getQuery();
            if (query != null) {
                this.setQuery(query);
            }

            final String path = parser.getPath();
            if ((path != null) && !path.isEmpty()) {
                this.setPath(path.startsWith("/") ? path.substring(1) : path);
            }

            return this;
        } catch (final URISyntaxException | IllegalArgumentException e) {
            throw new PEException("Malformed URL '" + urlString + "'", e);
        }
    }

    private Properties parseURLQuery(String urlQuery) throws PEException {
        Properties urlQProps = new Properties();

        String[] params = urlQuery.split("&");
        for (String param : params) {
            String[] queryElems = param.split("=");
            if (queryElems.length != 2)
                throw new PEException("Invalid options specified on URL");
            urlQProps.setProperty(queryElems[0], queryElems[1]);
        }

        return urlQProps;
    }

    private void calcQueryString() {
        query = "";
        for (String key : queryOptions.stringPropertyNames()) {
            if (!query.isEmpty())
                query += "&";

            query += key + "=" + queryOptions.getProperty(key);
        }
    }

    @Override
    public String toString() {
        try {
            return getURL();
        } catch (Exception e) {
            // ignore
        }
        return super.toString();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((host == null) ? 0 : host.hashCode());
        result = prime * result + ((path == null) ? 0 : path.hashCode());
        result = prime * result + port;
        result = prime * result + ((protocol == null) ? 0 : protocol.hashCode());
        result = prime * result + ((query == null) ? 0 : query.hashCode());
        result = prime * result + ((queryOptions == null) ? 0 : queryOptions.hashCode());
        result = prime * result + ((subProtocol == null) ? 0 : subProtocol.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;
        PEUrl other = (PEUrl) obj;
        if (host == null) {
            if (other.host != null)
                return false;
        } else if (!host.equals(other.host))
            return false;
        if (path == null) {
            if (other.path != null)
                return false;
        } else if (!path.equals(other.path))
            return false;
        if (port != other.port)
            return false;
        if (protocol == null) {
            if (other.protocol != null)
                return false;
        } else if (!protocol.equals(other.protocol))
            return false;
        if (query == null) {
            if (other.query != null)
                return false;
        } else if (!query.equals(other.query))
            return false;
        if (queryOptions == null) {
            if (other.queryOptions != null)
                return false;
        } else if (!queryOptions.equals(other.queryOptions))
            return false;
        if (subProtocol == null) {
            if (other.subProtocol != null)
                return false;
        } else if (!subProtocol.equals(other.subProtocol))
            return false;
        return true;
    }
}