org.alfresco.rest.api.tests.client.PublicApiHttpClient.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.rest.api.tests.client.PublicApiHttpClient.java

Source

/*
 * #%L
 * Alfresco Remote API
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.rest.api.tests.client;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.opencmis.CMISDispatcherRegistry.Binding;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.repo.tenant.TenantUtil;
import org.alfresco.repo.web.scripts.BaseWebScriptTest.PatchMethod;
import org.alfresco.rest.api.tests.client.AuthenticatedHttp.HttpRequestCallback;
import org.alfresco.rest.framework.Api;
import org.alfresco.rest.framework.Api.SCOPE;
import org.alfresco.rest.framework.core.ResourceInspector;
import org.alfresco.rest.framework.resource.EntityResource;
import org.alfresco.rest.framework.resource.RelationshipResource;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.util.Pair;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.methods.OptionsMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.methods.TraceMethod;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;

/**
 * A http client for talking to the rest apis. The caller can pass in a rest api
 * implementation class to the http method (get, post, put, delete supported)
 * and the url will be generated.
 *
 * @author steveglover
 */
public class PublicApiHttpClient {
    private static final Log logger = LogFactory.getLog(PublicApiHttpClient.class);

    private static final String OLD_BASE_URL = "{0}://{1}:{2}{3}{4}{5}/api/";
    private static final String INDEX_URL = "{0}://{1}:{2}{3}{4}";
    private static final String BASE_URL = "{0}://{1}:{2}{3}{4}{5}/{6}/{7}/versions/{8}";
    private static final String PUBLICAPI_CMIS_SERVICE_URL = "{0}://{1}:{2}{3}{4}cmis/versions/{5}/{6}";
    private static final String PUBLICAPI_CMIS_URL = "{0}://{1}:{2}{3}{4}{5}/{6}/cmis/versions/{7}/{8}";
    private static final String PUBLICAPI_CMIS_URL_SUFFIX = "{0}/{1}/cmis/versions/{2}/{3}";
    private static final String ATOM_PUB_URL = "{0}://{1}:{2}{3}cmisatom";

    private String scheme = "http";
    private String host = "localhost";
    private int port = 8081;

    private String contextPath;
    private String servletName;
    private AuthenticatedHttp authenticatedHttp;

    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(
            this.resourcePatternResolver);

    // can be overriden by other clients like the workflow client
    protected String apiName = "alfresco";

    public PublicApiHttpClient(String host, int port, String contextPath, String servletName,
            AuthenticatedHttp authenticatedHttp) {
        this("http", host, port, contextPath, servletName, authenticatedHttp);
    }

    public PublicApiHttpClient(String scheme, String host, int port, String contextPath, String servletName,
            AuthenticatedHttp authenticatedHttp) {
        super();
        this.scheme = scheme;
        this.host = host;
        this.port = port;
        this.contextPath = contextPath;
        if (this.contextPath != null && !this.contextPath.isEmpty() && !this.contextPath.endsWith("/")) {
            this.contextPath = this.contextPath + "/";
        }
        if (this.contextPath != null && !this.contextPath.startsWith("/")) {
            this.contextPath = "/" + this.contextPath;
        }
        this.servletName = servletName;
        if (this.servletName != null && !this.servletName.isEmpty() && !this.servletName.endsWith("/")) {
            this.servletName = this.servletName + "/";
        }
        this.authenticatedHttp = authenticatedHttp;
    }

    public String getCmisUrl(String repositoryId, String operation) {
        StringBuilder url = new StringBuilder();
        if (repositoryId == null) {
            url.append(MessageFormat.format(ATOM_PUB_URL,
                    new Object[] { scheme, host, String.valueOf(port), contextPath }));
        } else {
            url.append(MessageFormat.format(ATOM_PUB_URL,
                    new Object[] { scheme, host, String.valueOf(port), contextPath }));
            url.append("/");
            url.append(repositoryId);
        }

        if (operation != null) {
            url.append("/");
            url.append(operation);
        }

        return url.toString();
    }

    public String getPublicApiCmisUrl(String networkId, Binding binding, String version, String operation) {
        StringBuilder url = new StringBuilder();
        if (networkId == null) {
            url.append(MessageFormat.format(PUBLICAPI_CMIS_SERVICE_URL, new Object[] { scheme, host,
                    String.valueOf(port), contextPath, servletName, version, binding.toString().toLowerCase() }));
        } else {
            url.append(MessageFormat.format(PUBLICAPI_CMIS_URL, new Object[] { scheme, host, String.valueOf(port),
                    contextPath, servletName, networkId, "public", version, binding.toString().toLowerCase() }));
        }

        if (operation != null) {
            url.append("/");
            url.append(operation);
        }

        return url.toString();
    }

    public String getPublicApiCmisUrlSuffix(String networkId, Binding binding, String version, String operation) {
        StringBuilder url = new StringBuilder();

        url.append(MessageFormat.format(PUBLICAPI_CMIS_URL_SUFFIX,
                new Object[] { networkId, "public", version, binding.toString().toLowerCase() }));

        if (operation != null) {
            url.append("/");
            url.append(operation);
        }

        return url.toString();
    }

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

    public void setPort(int port) {
        this.port = port;
    }

    public void setContextPath(String contextPath) {
        this.contextPath = contextPath;
    }

    public void init() {
    }

    private void log(String msg) {
        if (logger.isDebugEnabled()) {
            logger.debug(msg);
        }
    }

    protected AnnotationMetadata getAnnotationMetadata(String classname) throws IOException {
        MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(classname);
        AnnotationMetadata annotationMetaData = metadataReader.getAnnotationMetadata();
        return annotationMetaData;
    }

    public HttpResponse submitRequest(HttpMethod req, final RequestContext rq) throws HttpException, IOException {
        try {
            final long start = System.currentTimeMillis();

            final HttpRequestCallback<HttpResponse> callback = new HttpRequestCallback<HttpResponse>() {
                @Override
                public HttpResponse onCallSuccess(HttpMethod method) throws Exception {
                    long end = System.currentTimeMillis();

                    Map<String, String> headersMap = null;
                    Header[] headers = method.getResponseHeaders();
                    if (headers != null) {
                        headersMap = new HashMap<String, String>(headers.length);
                        for (Header header : headers) {
                            headersMap.put(header.getName(), header.getValue());
                        }
                    }

                    return new HttpResponse(method, rq.getRunAsUser(), method.getResponseBody(), headersMap,
                            (end - start));
                }

                @Override
                public boolean onError(HttpMethod method, Throwable t) {
                    return false;
                }
            };

            HttpResponse response = null;
            if (rq.getPassword() != null) {
                response = authenticatedHttp.executeHttpMethodAuthenticated(req, rq.getRunAsUser(),
                        rq.getPassword(), callback);
            } else {
                response = authenticatedHttp.executeHttpMethodAuthenticated(req, rq.getRunAsUser(), callback);
            }
            return response;
        } finally {
            if (req != null) {
                req.releaseConnection();
            }
        }
    }

    public HttpResponse get(final RequestContext rq, final String urlSuffix, Map<String, String> params)
            throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), urlSuffix, params);
        String url = endpoint.getUrl();

        GetMethod req = new GetMethod(url);
        return submitRequest(req, rq);
    }

    public HttpResponse get(final Class<?> c, final RequestContext rq, final Object entityId,
            final Object relationshipEntityId, Map<String, String> params) throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(c, rq.getNetworkId(), entityId, relationshipEntityId,
                params);
        String url = endpoint.getUrl();

        GetMethod req = new GetMethod(url);
        return submitRequest(req, rq);
    }

    public HttpResponse get(final RequestContext rq, String scope, final String entityCollectionName,
            final Object entityId, final String relationCollectionName, final Object relationshipEntityId,
            Map<String, String> params) throws IOException {
        return get(rq, scope, 1, entityCollectionName, entityId, relationCollectionName, relationshipEntityId,
                params);
    }

    public HttpResponse get(final RequestContext rq, final String scope, final int version,
            final String entityCollectionName, final Object entityId, final String relationCollectionName,
            final Object relationshipEntityId, Map<String, String> params) throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), scope, version, entityCollectionName,
                entityId, relationCollectionName, relationshipEntityId, params);
        String url = endpoint.getUrl();

        GetMethod req = new GetMethod(url);
        return submitRequest(req, rq);
    }

    public HttpResponse get(final RequestContext rq, String scope, String password,
            final String entityCollectionName, final Object entityId, final String relationCollectionName,
            final Object relationshipEntityId, Map<String, String> params) throws IOException {
        return get(rq, scope, 1, password, entityCollectionName, entityId, relationCollectionName,
                relationshipEntityId, params);
    }

    public HttpResponse get(final RequestContext rq, final String scope, final int version, final String password,
            final String entityCollectionName, final Object entityId, final String relationCollectionName,
            final Object relationshipEntityId, Map<String, String> params) throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), scope, version, entityCollectionName,
                entityId, relationCollectionName, relationshipEntityId, params);
        String url = endpoint.getUrl();

        GetMethod req = new GetMethod(url);
        return submitRequest(req, rq);
    }

    public HttpResponse get(final String urlSuffix, final RequestContext rq, Map<String, String> params)
            throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(urlSuffix, params);
        String url = endpoint.getUrl();

        GetMethod req = new GetMethod(url);
        return submitRequest(req, rq);
    }

    public HttpResponse getWithPassword(final String urlSuffix, final RequestContext rq, Map<String, String> params)
            throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(urlSuffix, params);
        String url = endpoint.getUrl();

        GetMethod req = new GetMethod(url);
        return submitRequest(req, rq);
    }

    public HttpResponse post(final Class<?> c, final RequestContext rq, final Object entityId,
            final Object relationshipEntityId, final String body) throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(c, rq.getNetworkId(), entityId, relationshipEntityId, null);
        String url = endpoint.getUrl();

        PostMethod req = new PostMethod(url.toString());
        if (body != null) {
            StringRequestEntity requestEntity = new StringRequestEntity(body, "application/json", "UTF-8");
            req.setRequestEntity(requestEntity);
        }
        return submitRequest(req, rq);
    }

    public HttpResponse post(final RequestContext rq, final String urlSuffix, String body) throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), urlSuffix, null);
        String url = endpoint.getUrl();

        PostMethod req = new PostMethod(url.toString());
        if (body != null) {
            StringRequestEntity requestEntity = new StringRequestEntity(body, "application/json", "UTF-8");
            req.setRequestEntity(requestEntity);
        }
        return submitRequest(req, rq);
    }

    public HttpResponse post(final RequestContext rq, Binding cmisBinding, String version, String cmisOperation,
            final String body) throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), cmisBinding, version, cmisOperation,
                null);
        String url = endpoint.getUrl();

        PostMethod req = new PostMethod(url.toString());
        if (body != null) {
            StringRequestEntity requestEntity = null;
            if (cmisBinding.equals(Binding.atom)) {
                requestEntity = new StringRequestEntity(body, "text/xml", "UTF-8");
            } else if (cmisBinding.equals(Binding.browser)) {
                requestEntity = new StringRequestEntity(body, "application/json", "UTF-8");
            }
            req.setRequestEntity(requestEntity);
        }
        return submitRequest(req, rq);
    }

    public HttpResponse put(final RequestContext rq, Binding cmisBinding, String version, String cmisOperation,
            final String body) throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), cmisBinding, version, cmisOperation,
                null);
        String url = endpoint.getUrl();

        PutMethod req = new PutMethod(url.toString());
        if (body != null) {
            StringRequestEntity requestEntity = null;
            if (cmisBinding.equals(Binding.atom)) {
                requestEntity = new StringRequestEntity(body, "text/xml", "UTF-8");
            } else if (cmisBinding.equals(Binding.browser)) {
                requestEntity = new StringRequestEntity(body, "application/json", "UTF-8");
            }
            req.setRequestEntity(requestEntity);
        }
        return submitRequest(req, rq);
    }

    public HttpResponse get(final RequestContext rq, Binding cmisBinding, String version, String cmisOperation,
            Map<String, String> parameters) throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), cmisBinding, version, cmisOperation,
                parameters);
        String url = endpoint.getUrl();

        GetMethod req = new GetMethod(url.toString());
        return submitRequest(req, rq);
    }

    public HttpResponse delete(final RequestContext rq, Binding cmisBinding, String version, String cmisOperation)
            throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), cmisBinding, version, cmisOperation,
                null);
        String url = endpoint.getUrl();

        DeleteMethod req = new DeleteMethod(url.toString());
        return submitRequest(req, rq);
    }

    public HttpResponse head(final RequestContext rq, Binding cmisBinding, String version, String cmisOperation)
            throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), cmisBinding, version, cmisOperation,
                null);
        String url = endpoint.getUrl();

        HeadMethod req = new HeadMethod(url.toString());
        return submitRequest(req, rq);
    }

    public HttpResponse options(final RequestContext rq, Binding cmisBinding, String version, String cmisOperation)
            throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), cmisBinding, version, cmisOperation,
                null);
        String url = endpoint.getUrl();

        OptionsMethod req = new OptionsMethod(url.toString());
        return submitRequest(req, rq);
    }

    public HttpResponse trace(final RequestContext rq, Binding cmisBinding, String version, String cmisOperation)
            throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), cmisBinding, version, cmisOperation,
                null);
        String url = endpoint.getUrl();

        TraceMethod req = new TraceMethod(url.toString());
        return submitRequest(req, rq);
    }

    public HttpResponse patch(final RequestContext rq, Binding cmisBinding, String version, String cmisOperation)
            throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), cmisBinding, version, cmisOperation,
                null);
        String url = endpoint.getUrl();

        PatchMethod req = new PatchMethod(url.toString());
        return submitRequest(req, rq);
    }

    public HttpResponse post(final RequestContext rq, final String scope, final String entityCollectionName,
            final Object entityId, final String relationCollectionName, final Object relationshipEntityId,
            final String body) throws IOException {
        return post(rq, scope, entityCollectionName, entityId, relationCollectionName, relationshipEntityId, body,
                "application/json");
    }

    public HttpResponse post(final RequestContext rq, final String scope, final String entityCollectionName,
            final Object entityId, final String relationCollectionName, final Object relationshipEntityId,
            final String body, String contentType) throws IOException {
        return post(rq, scope, 1, entityCollectionName, entityId, relationCollectionName, relationshipEntityId,
                body, contentType);
    }

    public HttpResponse post(final RequestContext rq, final String scope, final int version,
            final String entityCollectionName, final Object entityId, final String relationCollectionName,
            final Object relationshipEntityId, final String body, String contentType) throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), scope, version, entityCollectionName,
                entityId, relationCollectionName, relationshipEntityId, null);
        String url = endpoint.getUrl();

        PostMethod req = new PostMethod(url.toString());
        if (body != null) {
            if (contentType == null || contentType.isEmpty()) {
                contentType = "application/json";
            }
            StringRequestEntity requestEntity = new StringRequestEntity(body, contentType, "UTF-8");
            req.setRequestEntity(requestEntity);
        }
        return submitRequest(req, rq);
    }

    public HttpResponse post(final RequestContext rq, final String scope, final String entityCollectionName,
            final Object entityId, final String relationCollectionName, final Object relationshipEntityId,
            final byte[] body, String contentType) throws IOException {
        return post(rq, scope, 1, entityCollectionName, entityId, relationCollectionName, relationshipEntityId,
                body, contentType);
    }

    public HttpResponse post(final RequestContext rq, final String scope, final int version,
            final String entityCollectionName, final Object entityId, final String relationCollectionName,
            final Object relationshipEntityId, final byte[] body, String contentType) throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), scope, version, entityCollectionName,
                entityId, relationCollectionName, relationshipEntityId, null);
        String url = endpoint.getUrl();

        PostMethod req = new PostMethod(url.toString());
        if (body != null) {
            if (contentType == null || contentType.isEmpty()) {
                contentType = "application/octet-stream";
            }
            ByteArrayRequestEntity requestEntity = new ByteArrayRequestEntity(body, contentType);
            req.setRequestEntity(requestEntity);
        }
        return submitRequest(req, rq);
    }

    public HttpResponse delete(final Class<?> c, final RequestContext rq, final Object entityId,
            final Object relationshipEntityId) throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(c, rq.getNetworkId(), entityId, relationshipEntityId, null);
        String url = endpoint.getUrl();

        DeleteMethod req = new DeleteMethod(url);
        return submitRequest(req, rq);
    }

    public HttpResponse delete(final RequestContext rq, final String scope, final String entityCollectionName,
            final Object entityId, final String relationCollectionName, final Object relationshipEntityId)
            throws IOException {
        return delete(rq, scope, 1, entityCollectionName, entityId, relationCollectionName, relationshipEntityId,
                null);
    }

    public HttpResponse delete(final RequestContext rq, final String scope, final int version,
            final String entityCollectionName, final Object entityId, final String relationCollectionName,
            final Object relationshipEntityId, final Map<String, String> params) throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), scope, version, entityCollectionName,
                entityId, relationCollectionName, relationshipEntityId, params);
        String url = endpoint.getUrl();

        DeleteMethod req = new DeleteMethod(url);
        return submitRequest(req, rq);
    }

    public HttpResponse put(final Class<?> c, final RequestContext rq, final Object entityId,
            final Object relationshipEntityId, final String body) throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(c, rq.getNetworkId(), entityId, relationshipEntityId, null);
        String url = endpoint.getUrl();

        PutMethod req = new PutMethod(url);
        if (body != null) {
            StringRequestEntity requestEntity = new StringRequestEntity(body, "application/json", "UTF-8");
            req.setRequestEntity(requestEntity);
        }
        return submitRequest(req, rq);
    }

    public HttpResponse put(final RequestContext rq, final String scope, final String entityCollectionName,
            final Object entityId, final String relationCollectionName, final Object relationshipEntityId,
            final String body, final Map<String, String> params) throws IOException {
        return put(rq, scope, 1, entityCollectionName, entityId, relationCollectionName, relationshipEntityId, body,
                params);
    }

    public HttpResponse put(final RequestContext rq, final String scope, final int version,
            final String entityCollectionName, final Object entityId, final String relationCollectionName,
            final Object relationshipEntityId, final String body, Map<String, String> params) throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), scope, version, entityCollectionName,
                entityId, relationCollectionName, relationshipEntityId, params);
        String url = endpoint.getUrl();

        PutMethod req = new PutMethod(url);
        if (body != null) {
            StringRequestEntity requestEntity = new StringRequestEntity(body, "application/json", "UTF-8");
            req.setRequestEntity(requestEntity);
        }
        return submitRequest(req, rq);
    }

    public HttpResponse putBinary(final RequestContext rq, final String scope, final int version,
            final String entityCollectionName, final Object entityId, final String relationCollectionName,
            final Object relationshipEntityId, final BinaryPayload payload, final Map<String, String> params)
            throws IOException {
        RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), scope, version, entityCollectionName,
                entityId, relationCollectionName, relationshipEntityId, params);
        String url = endpoint.getUrl();

        PutMethod req = new PutMethod(url);
        if (payload != null) {
            BinaryRequestEntity requestEntity = new BinaryRequestEntity(payload.getFile(), payload.getMimeType(),
                    payload.getCharset());
            req.setRequestEntity(requestEntity);
        }
        return submitRequest(req, rq);
    }

    public HttpResponse execute(RequestBuilder requestBuilder) throws IOException {
        return submitRequest(requestBuilder.getHttpMethod(), requestBuilder.getRequestContext());
    }

    /*
     * Encapsulates information relating to a rest api end point, generating and
     * encoding urls based on the rest api implementation class.
     */
    private class RestApiEndpoint {
        private String url;

        RestApiEndpoint(String url, Map<String, String> params) throws IOException {
            StringBuilder sb = new StringBuilder(MessageFormat.format(INDEX_URL,
                    new Object[] { scheme, host, String.valueOf(port), contextPath, servletName }));
            if (url != null) {
                sb.append(url);
            }

            addParams(sb, params);

            this.url = sb.toString();
        }

        RestApiEndpoint(String tenantDomain, String url, Map<String, String> params) throws IOException {
            StringBuilder sb = new StringBuilder(MessageFormat.format(OLD_BASE_URL,
                    new Object[] { scheme, host, String.valueOf(port), contextPath, servletName,
                            tenantDomain == null ? TenantUtil.DEFAULT_TENANT : tenantDomain }));
            sb.append("/");
            sb.append(url);

            addParams(sb, params);

            this.url = sb.toString();
        }

        RestApiEndpoint(Class<?> resourceClass, String tenantDomain, Object collectionEntityId,
                Object relationEntityId, Map<String, String> params) throws IOException {
            StringBuilder sb = new StringBuilder();

            Api api = ResourceInspector.inspectApi(resourceClass);
            SCOPE scope = api.getScope();
            int version = api.getVersion();
            String name = api.getName();
            if (StringUtils.isEmpty(name)) {
                name = apiName;
            }

            Pair<String, String> relationshipCollectionInfo = getRelationCollectionInfo(resourceClass);

            sb.append(MessageFormat.format(BASE_URL,
                    new Object[] { scheme, host, String.valueOf(port), contextPath, servletName,
                            tenantDomain == null ? TenantUtil.DEFAULT_TENANT : tenantDomain, scope.toString(), name,
                            version }));

            if (relationshipCollectionInfo != null) {
                String entityCollectionName = relationshipCollectionInfo.getFirst();
                String relationshipCollectionName = relationshipCollectionInfo.getSecond();
                String relationEntityIdString = encodeToString(relationEntityId);
                String collectionEntityIdString = encodeToString(collectionEntityId);

                sb.append('/');
                sb.append(entityCollectionName);
                sb.append('/');
                if (collectionEntityIdString != null) {
                    sb.append(collectionEntityIdString);
                    sb.append('/');
                }

                sb.append(relationshipCollectionName);
                sb.append('/');
                if (relationEntityIdString != null) {
                    sb.append(relationEntityIdString);
                    sb.append('/');
                }
            } else {
                String entityCollectionName = getEntityCollectionInfo(resourceClass);
                if (entityCollectionName != null) {
                    String collectionEntityIdString = encodeToString(collectionEntityId);

                    sb.append('/');
                    sb.append(entityCollectionName);
                    sb.append('/');
                    if (collectionEntityIdString != null) {
                        sb.append(collectionEntityIdString);
                        sb.append('/');
                    }
                } else {
                    throw new RuntimeException();
                }
            }

            addParams(sb, params);

            this.url = sb.toString();
        }

        RestApiEndpoint(String networkId, Binding cmisBinding, String version, String cmisOperation,
                Map<String, String> params) throws IOException {
            StringBuilder sb = new StringBuilder();

            if (networkId != null) {
                sb.append(getPublicApiCmisUrl(networkId, cmisBinding, version, cmisOperation));
            } else {
                throw new IllegalArgumentException();
            }

            addParams(sb, params);

            this.url = sb.toString();
        }

        RestApiEndpoint(String tenantDomain, String scope, int version, String collectionName,
                Object collectionEntityId, String relationName, Object relationEntityId, Map<String, String> params)
                throws IOException {
            this(tenantDomain, scope, apiName, version, collectionName, collectionEntityId, relationName,
                    relationEntityId, params);
        }

        RestApiEndpoint(String tenantDomain, String scope, String apiName, int version, String collectionName,
                Object collectionEntityId, String relationName, Object relationEntityId, Map<String, String> params)
                throws IOException {
            StringBuilder sb = new StringBuilder();

            if (tenantDomain == null || tenantDomain.equals(TenantService.DEFAULT_DOMAIN)) {
                tenantDomain = TenantUtil.DEFAULT_TENANT;
            }

            if (StringUtils.isEmpty(apiName)) {
                apiName = PublicApiHttpClient.this.apiName;
            }

            sb.append(MessageFormat.format(BASE_URL, new Object[] { scheme, host, String.valueOf(port), contextPath,
                    servletName, tenantDomain, scope, apiName, version }));

            if (collectionName != null) {
                sb.append('/');
                sb.append(collectionName);
                if (collectionEntityId != null) {
                    sb.append('/');
                    sb.append(collectionEntityId);
                }
            }

            if (relationName != null) {
                sb.append('/');
                sb.append(relationName);
                if (relationEntityId != null) {
                    sb.append('/');
                    sb.append(relationEntityId);
                }
            }

            addParams(sb, params);

            this.url = sb.toString();
        }

        RestApiEndpoint(String tenantDomain, String scope, String collectionName, Object collectionEntityId,
                String relationName, Object relationEntityId, Map<String, String> params) throws IOException {
            this(tenantDomain, scope, 1, collectionName, collectionEntityId, relationName, relationEntityId,
                    params);
        }

        private void addParams(StringBuilder sb, Map<String, String> params) throws UnsupportedEncodingException {
            if (params != null && params.size() > 0) {
                sb.append("?");

                for (String paramName : params.keySet()) {
                    sb.append(URLEncoder.encode(paramName, "UTF-8"));
                    sb.append("=");
                    sb.append(URLEncoder.encode(params.get(paramName), "UTF-8"));
                    sb.append("&");
                }

                sb.deleteCharAt(sb.length() - 1);
            }
        }

        private String encodeToString(Object o) throws UnsupportedEncodingException {
            String ret = null;

            if (o instanceof NodeRef) {
                NodeRef nodeRef = (NodeRef) o;
                ret = (o != null ? nodeRef.getId() : null);
            } else {
                ret = (o != null ? o.toString() : null);
            }

            return ret;
        }

        private Pair<String, String> getRelationCollectionInfo(Class<?> resourceClass) throws IOException {
            AnnotationMetadata annotationMetaData = getAnnotationMetadata(resourceClass.getCanonicalName());
            if (annotationMetaData.isConcrete() && annotationMetaData.isIndependent()) {
                if (annotationMetaData.getAnnotationTypes()
                        .contains(RelationshipResource.class.getCanonicalName())) {
                    Map<String, Object> attrs = annotationMetaData
                            .getAnnotationAttributes(RelationshipResource.class.getName());
                    String relationshipCollectionName = (String) attrs.get("name");
                    Class<?> entityResource = (Class<?>) attrs.get("entityResource");

                    String entityCollectionName = getEntityCollectionInfo(entityResource.getCanonicalName());

                    Pair<String, String> ret = new Pair<String, String>(entityCollectionName,
                            relationshipCollectionName);
                    return ret;
                } else {
                    return null;
                }
            } else {
                throw new AlfrescoRuntimeException("");
            }
        }

        private String getEntityCollectionInfo(Class<?> resourceClass) throws IOException {
            return getEntityCollectionInfo(resourceClass.getCanonicalName());
        }

        private String getEntityCollectionInfo(String className) throws IOException {
            AnnotationMetadata annotationMetaData = getAnnotationMetadata(className);
            if (annotationMetaData.isConcrete() && annotationMetaData.isIndependent()) {
                if (annotationMetaData.getAnnotationTypes().contains(EntityResource.class.getCanonicalName())) {
                    Map<String, Object> attrs = annotationMetaData
                            .getAnnotationAttributes(EntityResource.class.getName());
                    return (String) attrs.get("name");
                } else {
                    return null;
                }
            } else {
                throw new AlfrescoRuntimeException("");
            }
        }

        public String getUrl() throws UnsupportedEncodingException {
            return url;
        }
    }

    /**
     * @author Jamal Kaabi-Mofrad
     */
    public static class BinaryRequestEntity implements RequestEntity {
        private final File file;
        private final String mimeType;
        private final String charset;

        public BinaryRequestEntity(File file, String mimeType, String charset) {
            this.file = file;
            this.mimeType = mimeType;
            this.charset = charset;
        }

        @Override
        public boolean isRepeatable() {
            return true;
        }

        @Override
        public void writeRequest(OutputStream out) throws IOException {
            InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
            try {
                int len;
                byte[] buffer = new byte[8190];
                while ((len = inputStream.read(buffer)) != -1) {
                    out.write(buffer, 0, len);
                }
            } finally {
                inputStream.close();
            }
        }

        @Override
        public long getContentLength() {
            return file.length();
        }

        @Override
        public String getContentType() {
            if (charset == null) {
                return mimeType;
            }
            if (mimeType == null) {
                return null;
            }
            return mimeType + "; charset=" + charset;
        }
    }

    /**
     * @author Jamal Kaabi-Mofrad
     */
    public static class BinaryPayload {
        private File file;
        private String mimeType;
        private String charset;

        public BinaryPayload(File file, String mimeType, String charset) {
            this.file = file;
            this.mimeType = mimeType;
            this.charset = charset;
        }

        public BinaryPayload(File file, String mimeType) {
            this(file, mimeType, null);
        }

        public BinaryPayload(File file) {
            this(file, null, null);
        }

        public File getFile() {
            return file;
        }

        public String getMimeType() {
            return mimeType;
        }

        public String getCharset() {
            return charset;
        }
    }

    /**
     * @author Jamal Kaabi-Mofrad
     */
    public abstract class RequestBuilder<T extends HttpMethod> {
        private RequestContext requestContext;
        private String scope;
        private String apiName = "alfresco"; // default api namespace
        private int version = 1;
        private String entityCollectionName;
        private Object entityId;
        private String relationCollectionName;
        private Object relationshipEntityId;
        private Map<String, String> params;
        private Map<String, String> headers;

        public abstract T getHttpMethod() throws IOException;

        protected void setRequestHeaderIfAny(T methodBase) {
            if (headers != null) {
                for (Entry<String, String> headerNameValue : headers.entrySet()) {
                    methodBase.addRequestHeader(headerNameValue.getKey(), headerNameValue.getValue());
                }
            }
        }

        public RequestContext getRequestContext() {
            return requestContext;
        }

        public RequestBuilder setRequestContext(RequestContext requestContext) {
            this.requestContext = requestContext;
            return this;
        }

        public String getScope() {
            return scope;
        }

        public RequestBuilder setScope(String scope) {
            this.scope = scope;
            return this;
        }

        public String getApiName() {
            return apiName;
        }

        public RequestBuilder setApiName(String apiName) {
            this.apiName = apiName;
            return this;
        }

        public int getVersion() {
            return version;
        }

        public RequestBuilder setVersion(int version) {
            this.version = version;
            return this;
        }

        public String getEntityCollectionName() {
            return entityCollectionName;
        }

        public RequestBuilder setEntityCollectionName(String entityCollectionName) {
            this.entityCollectionName = entityCollectionName;
            return this;
        }

        public Object getEntityId() {
            return entityId;
        }

        public RequestBuilder setEntityId(Object entityId) {
            this.entityId = entityId;
            return this;
        }

        public String getRelationCollectionName() {
            return relationCollectionName;
        }

        public RequestBuilder setRelationCollectionName(String relationCollectionName) {
            this.relationCollectionName = relationCollectionName;
            return this;
        }

        public Object getRelationshipEntityId() {
            return relationshipEntityId;
        }

        public RequestBuilder setRelationshipEntityId(Object relationshipEntityId) {
            this.relationshipEntityId = relationshipEntityId;
            return this;
        }

        public Map<String, String> getParams() {
            return params;
        }

        public RequestBuilder setParams(Map<String, String> params) {
            this.params = params;
            return this;
        }

        public Map<String, String> getHeaders() {
            return headers;
        }

        public RequestBuilder setHeaders(Map<String, String> headers) {
            this.headers = headers;
            return this;
        }
    }

    public class GetRequestBuilder extends RequestBuilder<GetMethod> {
        private String password;

        public String getPassword() {
            return password;
        }

        public GetRequestBuilder setPassword(String password) {
            this.password = password;
            return this;
        }

        @Override
        public GetMethod getHttpMethod() throws IOException {
            RestApiEndpoint endpoint = new RestApiEndpoint(getRequestContext().getNetworkId(), getScope(),
                    getApiName(), getVersion(), getEntityCollectionName(), getEntityId(),
                    getRelationCollectionName(), getRelationshipEntityId(), getParams());
            String url = endpoint.getUrl();

            GetMethod req = new GetMethod(url);
            setRequestHeaderIfAny(req);
            return req;
        }
    }

    public class DeleteRequestBuilder extends RequestBuilder<DeleteMethod> {
        @Override
        public DeleteMethod getHttpMethod() throws IOException {
            RestApiEndpoint endpoint = new RestApiEndpoint(getRequestContext().getNetworkId(), getScope(),
                    getApiName(), getVersion(), getEntityCollectionName(), getEntityId(),
                    getRelationCollectionName(), getRelationshipEntityId(), getParams());
            String url = endpoint.getUrl();

            DeleteMethod req = new DeleteMethod(url);
            setRequestHeaderIfAny(req);
            return req;
        }
    }

    public abstract class PostPutRequestBuilder<T extends HttpMethodBase> extends RequestBuilder<T> {
        private String bodyAsString;
        private byte[] bodyAsByteArray;
        private String contentType;

        public String getBodyAsString() {
            return bodyAsString;
        }

        public PostPutRequestBuilder setBodyAsString(String bodyAsString) {
            this.bodyAsString = bodyAsString;
            return this;
        }

        public byte[] getBodyAsByteArray() {
            return bodyAsByteArray;
        }

        public PostPutRequestBuilder setBodyAsByteArray(byte[] bodyAsByteArray) {
            this.bodyAsByteArray = bodyAsByteArray;
            return this;
        }

        public String getContentType() {
            return contentType;
        }

        public PostPutRequestBuilder setContentType(String contentType) {
            this.contentType = contentType;
            return this;
        }
    }

    public class PostRequestBuilder extends PostPutRequestBuilder<PostMethod> {

        @Override
        public PostMethod getHttpMethod() throws IOException {
            RestApiEndpoint endpoint = new RestApiEndpoint(getRequestContext().getNetworkId(), getScope(),
                    getApiName(), getVersion(), getEntityCollectionName(), getEntityId(),
                    getRelationCollectionName(), getRelationshipEntityId(), getParams());
            String url = endpoint.getUrl();

            PostMethod req = new PostMethod(url.toString());
            String contentType = getContentType();
            if (getBodyAsString() != null) {

                if (contentType == null || contentType.isEmpty()) {
                    contentType = "application/json";
                }
                StringRequestEntity requestEntity = new StringRequestEntity(getBodyAsString(), contentType,
                        "UTF-8");
                req.setRequestEntity(requestEntity);
            } else if (getBodyAsByteArray() != null) {
                if (contentType == null || contentType.isEmpty()) {
                    contentType = "application/octet-stream";
                }
                ByteArrayRequestEntity requestEntity = new ByteArrayRequestEntity(getBodyAsByteArray(),
                        contentType);
                req.setRequestEntity(requestEntity);
            }
            setRequestHeaderIfAny(req);
            return req;
        }
    }

    public class PutRequestBuilder extends PostPutRequestBuilder<PutMethod> {
        private BinaryPayload binaryPayload;

        public BinaryPayload getBinaryPayload() {
            return binaryPayload;
        }

        public PutRequestBuilder setBinaryPayload(BinaryPayload binaryPayload) {
            this.binaryPayload = binaryPayload;
            return this;
        }

        @Override
        public PutMethod getHttpMethod() throws IOException {
            RestApiEndpoint endpoint = new RestApiEndpoint(getRequestContext().getNetworkId(), getScope(),
                    getApiName(), getVersion(), getEntityCollectionName(), getEntityId(),
                    getRelationCollectionName(), getRelationshipEntityId(), getParams());
            String url = endpoint.getUrl();

            PutMethod req = new PutMethod(url);
            if (getBodyAsString() != null) {
                StringRequestEntity requestEntity = new StringRequestEntity(getBodyAsString(), "application/json",
                        "UTF-8");
                req.setRequestEntity(requestEntity);
            } else if (binaryPayload != null) {
                BinaryRequestEntity requestEntity = new BinaryRequestEntity(binaryPayload.getFile(),
                        binaryPayload.getMimeType(), binaryPayload.getCharset());
                req.setRequestEntity(requestEntity);
            }
            setRequestHeaderIfAny(req);
            return req;
        }
    }
}