org.mule.endpoint.URIBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.mule.endpoint.URIBuilder.java

Source

/*
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */
package org.mule.endpoint;

import static java.net.URLEncoder.encode;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.commons.collections.TransformerUtils.nopTransformer;

import org.mule.AbstractAnnotatedObject;
import org.mule.api.MuleContext;
import org.mule.api.endpoint.EndpointException;
import org.mule.api.endpoint.EndpointURI;
import org.mule.util.ClassUtils;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.collections.Transformer;

/**
 * This has the following logic:
 * - if an address is specified, it is used verbatim (except for parameters); this is consistent with the generic case
 * - otherwise, we construct from components, omitting things that aren't specified as much as possible
 * (use required attributes to guarantee entries)
 *
 * In addition, parameters are handled as follows:
 * - parameters can be given in the uri, the queryMap, or both
 * - queryMap values override uri values
 * - the order of parameters in the uri remains the same (even if values change)
 * - queryMap parameters are appended after uri parameters
 *
 * TODO - check that we have sufficient control via XML (what about empty strings?)
 *
 * Not called EndpointURIBuilder because of {@link org.mule.api.endpoint.EndpointURIBuilder}
 *
 */
public class URIBuilder extends AbstractAnnotatedObject {
    private static final String DOTS = ":";
    private static final String DOTS_SLASHES = DOTS + "//";
    private static final String SLASH = "/";
    private static final String QUERY = "?";
    private static final String AND = "&";
    private static final String EQUALS = "=";
    private static final String BACKSLASH = "\\";

    public static final String META = "meta";
    public static final String PROTOCOL = "protocol";
    public static final String USER = "user";
    public static final String PASSWORD = "password";
    public static final String HOST = "host";
    public static final String ADDRESS = "address";
    public static final String PORT = "port";
    public static final String PATH = "path";

    public static final String[] ALL_ATTRIBUTES = new String[] { META, PROTOCOL, USER, PASSWORD, HOST, ADDRESS,
            PORT, PATH };
    // combinations used in various endpoint parsers to validate required attributes
    public static final String[] PATH_ATTRIBUTES = new String[] { PATH };
    public static final String[] HOST_ATTRIBUTES = new String[] { HOST };
    public static final String[] SOCKET_ATTRIBUTES = new String[] { HOST, PORT };
    public static final String[] USERHOST_ATTRIBUTES = new String[] { USER, HOST };
    // this doesn't include address, since that is handled separately (and is exclusive with these)
    public static final String[] ALL_TRANSPORT_ATTRIBUTES = new String[] { USER, PASSWORD, HOST, PORT, PATH };

    protected static final Transformer URL_ENCODER = new Transformer() {
        @Override
        public Object transform(Object input) {
            try {
                return encode((String) input, UTF_8.name());
            } catch (UnsupportedEncodingException e) {
                throw new AssertionError("UTF-8 is unknown");
            }
        }
    };

    private String address;
    private String meta;
    private String protocol;
    private String user;
    private String password;
    private String host;
    private String port;
    private String path;
    private Map queryMap;
    private MuleContext muleContext;

    private AtomicReference<EndpointURI> cache = new AtomicReference<EndpointURI>();

    public URIBuilder() {
        //default for spring. Must call setMulecontext().
    }

    public URIBuilder(MuleContext muleContext) {
        this.muleContext = muleContext;
    }

    public URIBuilder(EndpointURI endpointURI) {
        this(endpointURI.getMuleContext());
        cache.set(endpointURI);
    }

    public URIBuilder(String address, MuleContext muleContext) {
        this(muleContext);
        // separate meta from address, if necessary
        int dots = address.indexOf(DOTS);
        int dotsSlashes = address.indexOf(DOTS_SLASHES);
        if (dots > -1 && dots < dotsSlashes) {
            this.meta = address.substring(0, dots);
            address = address.substring(dots + 1);
        }
        this.address = address;
    }

    public MuleContext getMuleContext() {
        return muleContext;
    }

    public void setMuleContext(MuleContext muleContext) {
        this.muleContext = muleContext;
    }

    public void setUser(String user) {
        assertNotUsed();
        this.user = user;
    }

    public void setPassword(String password) {
        assertNotUsed();
        this.password = password;
    }

    public void setHost(String host) {
        assertNotUsed();
        this.host = host;
    }

    public void setAddress(String address) {
        assertNotUsed();
        this.address = address;
        assertAddressConsistent();
    }

    /**
     * For backwards compatibility
     */
    public void setPort(int port) {
        assertNotUsed();
        this.port = Integer.toString(port);
    }

    /**
     * Allows ports to be Mule expressions
     */
    public void setPort(String port) {
        assertNotUsed();
        this.port = port;
    }

    public void setProtocol(String protocol) {
        assertNotUsed();
        this.protocol = protocol;
        assertAddressConsistent();
    }

    public void setMeta(String meta) {
        assertNotUsed();
        this.meta = meta;
    }

    public void setPath(String path) {
        assertNotUsed();
        if (null != path) {
            if (path.indexOf(DOTS_SLASHES) > -1) {
                throw new IllegalArgumentException(
                        "Unusual syntax in path: '" + path + "' contains " + DOTS_SLASHES);
            } else if (path.contains(BACKSLASH)) {
                // Windows syntax.  convert it to URI syntax
                try {
                    URI pathUri = new File(path).toURI();
                    path = pathUri.getPath();
                } catch (Exception ex) {
                    throw new IllegalArgumentException("Illegal syntax in path: " + path, ex);
                }
            }
        }
        this.path = path;
    }

    public void setQueryMap(Map queryMap) {
        assertNotUsed();
        this.queryMap = queryMap;
    }

    public EndpointURI getEndpoint() {
        if (null == cache.get()) {
            try {
                EndpointURI endpointUri = new MuleEndpointURI(getConstructor(), getEncodedConstructor(),
                        muleContext);
                cache.compareAndSet(null, endpointUri);
            } catch (EndpointException e) {
                throw (IllegalStateException) new IllegalStateException("Bad endpoint configuration").initCause(e);
            }
        }
        return cache.get();
    }

    /**
     * @return The String supplied to the delegate constructor
     */
    protected String getConstructor() {
        return getTransformedConstructor(nopTransformer(), nopTransformer());
    }

    protected String getEncodedConstructor() {
        return getTransformedConstructor(nopTransformer(), URL_ENCODER);
    }

    protected String getTransformedConstructor(Transformer tokenProcessor, Transformer tokenEncoder) {
        StringBuilder buffer = new StringBuilder();
        appendMeta(buffer);
        OrderedQueryParameters uriQueries = appendAddress(buffer, tokenProcessor, tokenEncoder);
        uriQueries.override(queryMap);
        buffer.append(uriQueries.toString());
        removeRootTrailingSlash(buffer);
        return buffer.toString();
    }

    private void appendMeta(StringBuilder buffer) {
        if (null != meta) {
            buffer.append(meta);
            buffer.append(DOTS);
        }
    }

    private OrderedQueryParameters appendAddress(StringBuilder buffer, Transformer tokenProcessor,
            Transformer tokenEncoder) {
        if (null != address) {
            int index = address.indexOf(QUERY);
            if (index > -1) {
                buffer.append(tokenProcessor.transform(address.substring(0, index)));
                return parseQueries((String) tokenProcessor.transform(address.substring(index + 1)));
            } else {
                buffer.append(tokenProcessor.transform(address));
                return new OrderedQueryParameters();
            }
        } else {
            constructAddress(buffer, tokenProcessor, tokenEncoder);
            return new OrderedQueryParameters();
        }
    }

    private OrderedQueryParameters parseQueries(String queries) {
        OrderedQueryParameters map = new OrderedQueryParameters();
        StringTokenizer query = new StringTokenizer(queries, AND);
        while (query.hasMoreTokens()) {
            StringTokenizer nameValue = new StringTokenizer(query.nextToken(), EQUALS);
            String name = nameValue.nextToken();
            String value = null;
            if (nameValue.hasMoreTokens()) {
                value = nameValue.nextToken();
            }
            map.put(name, value);
        }
        return map;
    }

    private void constructAddress(StringBuilder buffer, Transformer tokenProcessor, Transformer tokenEncoder) {
        buffer.append(protocol);
        buffer.append(DOTS_SLASHES);
        boolean atStart = true;
        if (null != user) {
            buffer.append(tokenEncoder.transform(tokenProcessor.transform(user)));
            if (null != password) {
                buffer.append(":");
                buffer.append(tokenEncoder.transform(tokenProcessor.transform(password)));
            }
            buffer.append("@");
            atStart = false;
        }
        if (null != host) {
            buffer.append(tokenProcessor.transform(host));
            if (null != port) {
                buffer.append(":");
                buffer.append(tokenProcessor.transform(port));
            }
            atStart = false;
        }
        if (null != path) {
            if (!atStart && !path.startsWith("/")) {
                buffer.append("/");
            }
            buffer.append(tokenProcessor.transform(path));
        }
    }

    private void removeRootTrailingSlash(StringBuilder buffer) {
        int lastIndex = buffer.length() - 1;

        if (lastIndex >= 0 && buffer.charAt(lastIndex) == SLASH.charAt(0)) {
            int start = 0;
            int index = buffer.indexOf(DOTS_SLASHES);

            if (index != -1) {
                start = index + DOTS_SLASHES.length();
            }

            if (buffer.indexOf(SLASH, start) == lastIndex) {
                buffer.deleteCharAt(lastIndex);
            }
        }
    }

    protected void assertNotUsed() {
        if (null != cache.get()) {
            throw new IllegalStateException("Too late to set values - builder already used");
        }
    }

    protected void assertAddressConsistent() {
        if (null != meta) {
            if (null != address) {
                if (address.startsWith(meta + DOTS)) {
                    throw new IllegalArgumentException(
                            "Meta-protocol '" + meta + "' should not be specified in the address '" + address
                                    + "' - it is implicit in the element namespace.");
                }
                if (null != protocol) {
                    assertProtocolConsistent();
                } else {
                    if (address.indexOf(DOTS_SLASHES) == -1) {
                        throw new IllegalArgumentException(
                                "Address '" + address + "' does not have a transport protocol prefix "
                                        + "(omit the meta protocol prefix, '" + meta + DOTS
                                        + "' - it is implicit in the element namespace).");
                    }
                }
            }
        } else {
            assertProtocolConsistent();
        }
    }

    protected void assertProtocolConsistent() {
        if (null != protocol && null != address && !address.startsWith(protocol + DOTS_SLASHES)) {
            throw new IllegalArgumentException("Address '" + address + "' for protocol '" + protocol
                    + "' should start with " + protocol + DOTS_SLASHES);
        }
    }

    @Override
    public String toString() {
        return getConstructor();
    }

    @Override
    public boolean equals(Object other) {
        if (null == other || !getClass().equals(other.getClass()))
            return false;
        if (this == other)
            return true;

        URIBuilder builder = (URIBuilder) other;
        return equal(address, builder.address) && equal(meta, builder.meta) && equal(protocol, builder.protocol)
                && equal(user, builder.user) && equal(password, builder.password) && equal(host, builder.host)
                && equal(port, builder.port) && equal(path, builder.path) && equal(queryMap, builder.queryMap);
    }

    protected static boolean equal(Object a, Object b) {
        return ClassUtils.equal(a, b);
    }

    @Override
    public int hashCode() {
        return ClassUtils
                .hash(new Object[] { address, meta, protocol, user, password, host, port, path, queryMap });
    }

    private static class OrderedQueryParameters {
        private List<String> names = new ArrayList<String>();
        private List<String> values = new ArrayList<String>();

        public void put(String name, String value) {
            names.add(name);
            values.add(value);
        }

        /**
         * Replace the first instance of the given parameter. This method does not make sense under the assumption that
         * a given parameter name can have multiple values, so here we simply preserve the existing semantics.
         * @param map A map off the name/value pairs to add/replace in the query string
         */
        public void override(Map map) {
            if (null != map) {
                // order additional parameters
                Iterator mapNames = new TreeMap(map).keySet().iterator();
                while (mapNames.hasNext()) {
                    String name = (String) mapNames.next();
                    String value = (String) map.get(name);

                    int pos = names.indexOf(name);
                    if (pos >= 0) {
                        // Found, so replace
                        values.set(pos, value);
                    } else {
                        // Append new value
                        names.add(name);
                        values.add(value);
                    }
                }
            }
        }

        @Override
        public String toString() {
            StringBuilder buffer = new StringBuilder();

            boolean first = true;

            for (int i = 0; i < names.size(); i++) {
                if (first) {
                    buffer.append(QUERY);
                    first = false;
                } else {
                    buffer.append(AND);
                }

                buffer.append(names.get(i));
                String value = values.get(i);

                if (null != value) {
                    buffer.append(EQUALS);
                    buffer.append(value);
                }
            }
            return buffer.toString();
        }
    }

    public String getProtocol() {
        return protocol;
    }

    public String getMeta() {
        return meta;
    }

    public String getUser() {
        return user;
    }

    public String getPassword() {
        return password;
    }

    public String getHost() {
        return host;
    }

    public String getPort() {
        return port;
    }

    public String getPath() {
        return path;
    }

    public String getAddress() {
        return address;
    }

    public Map getQueryMap() {
        return queryMap;
    }

}