com.ctriposs.rest4j.client.Request.java Source code

Java tutorial

Introduction

Here is the source code for com.ctriposs.rest4j.client.Request.java

Source

/**
 * Copyright (C) 2014 the original author or authors.
 * See the notice.md file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ctriposs.rest4j.client;

import com.ctriposs.rest4j.common.ComplexResourceKey;
import com.ctriposs.rest4j.common.CompoundKey;
import com.ctriposs.rest4j.common.HttpMethod;
import com.ctriposs.rest4j.common.ResourceMethod;
import com.ctriposs.rest4j.common.ResourceSpec;
import com.ctriposs.rest4j.common.RestConstants;
import com.ctriposs.data.DataComplex;
import com.ctriposs.data.DataMap;
import com.ctriposs.data.schema.PathSpec;
import com.ctriposs.data.template.DataTemplate;
import com.ctriposs.data.template.RecordTemplate;
import com.ctriposs.jersey.api.uri.UriTemplate;
import com.ctriposs.rest4j.client.uribuilders.Rest4JUriBuilderUtil;
import com.ctriposs.rest4j.internal.client.QueryParamsUtil;
import com.ctriposs.rest4j.internal.client.RestResponseDecoder;

import java.lang.reflect.Array;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

/**
 * A type-bound Request for a resource.
 *
 * @param <T> response entity template class
 *
 */
public class Request<T> {
    private static final Pattern SLASH_PATTERN = Pattern.compile("/");

    /*
    A Request object should not have a URI anymore. The _uri and _hasUri fields are present here only because of
    older clients using one of the deprecated constructors that have a URI parameter. If one of these constructors are
    used we set the _uri field to the passed in URI and set _hasUri to true.
        
    Similarly, if someone calls the deprecated getUri method we will either
      (a) return _uri if _hasUri has been set to true
      (b) generate a uri using the Rest4JUriBuilderUtil, and store it in _uri
        
    These two fields will be removed in the future.
    */
    private URI _uri;
    private boolean _hasUri;

    private final ResourceMethod _method;
    private final RecordTemplate _inputRecord;
    private final RestResponseDecoder<T> _decoder;
    private final Map<String, String> _headers;
    private final ResourceSpec _resourceSpec;
    private final Map<String, Object> _queryParams;
    private final String _methodName; // needed to identify finders and actions. null for everything else
    private final String _baseUriTemplate;
    private final Map<String, Object> _pathKeys;
    private final Rest4JRequestOptions _requestOptions;

    @Deprecated
    public Request(URI uri, ResourceMethod method, RecordTemplate inputRecord, Map<String, String> headers,
            RestResponseDecoder<T> decoder, ResourceSpec resourceSpec) {
        this(uri, method, inputRecord, headers, decoder, resourceSpec, Collections.<String>emptyList());
    }

    @Deprecated
    public Request(URI uri, ResourceMethod method, RecordTemplate inputRecord, Map<String, String> headers,
            RestResponseDecoder<T> decoder, ResourceSpec resourceSpec, DataMap queryParams) {
        this(uri, method, inputRecord, headers, decoder, resourceSpec, queryParams,
                Collections.<String>emptyList());
    }

    @Deprecated
    public Request(URI uri, ResourceMethod method, RecordTemplate inputRecord, Map<String, String> headers,
            RestResponseDecoder<T> decoder, ResourceSpec resourceSpec, List<String> resourcePath) {
        this(uri, method, inputRecord, headers, decoder, resourceSpec, null, resourcePath);
    }

    @Deprecated
    public Request(URI uri, ResourceMethod method, RecordTemplate inputRecord, Map<String, String> headers,
            RestResponseDecoder<T> decoder, ResourceSpec resourceSpec, DataMap queryParams,
            List<String> resourcePath) {
        this(uri, method, inputRecord, headers, decoder, resourceSpec, queryParams, resourcePath, null);
    }

    @Deprecated
    public Request(URI uri, ResourceMethod method, RecordTemplate inputRecord, Map<String, String> headers,
            RestResponseDecoder<T> decoder, ResourceSpec resourceSpec, DataMap queryParams,
            List<String> resourcePath, String methodName) {
        _method = method;
        _inputRecord = inputRecord;
        _headers = (headers == null) ? null : Collections.unmodifiableMap(headers);
        _decoder = decoder;
        _queryParams = (queryParams == null) ? null : Collections.unmodifiableMap(queryParams);
        _resourceSpec = resourceSpec;
        _methodName = methodName;
        _baseUriTemplate = null;
        _pathKeys = null;
        _uri = uri;
        _hasUri = true;
        _requestOptions = Rest4JRequestOptions.DEFAULT_OPTIONS;
    }

    Request(ResourceMethod method, RecordTemplate inputRecord, Map<String, String> headers,
            RestResponseDecoder<T> decoder, ResourceSpec resourceSpec, Map<String, Object> queryParams,
            String methodName, String baseUriTemplate, Map<String, Object> pathKeys,
            Rest4JRequestOptions requestOptions) {
        _method = method;
        _inputRecord = inputRecord;
        _decoder = decoder;
        _headers = headers == null ? null : Collections.unmodifiableMap(headers);
        _resourceSpec = resourceSpec;

        if (queryParams == null) {
            _queryParams = null;
        } else {
            _queryParams = getReadOnlyQueryParams(queryParams);
        }

        _methodName = methodName;

        _baseUriTemplate = baseUriTemplate;

        _pathKeys = (pathKeys == null) ? null : Collections.unmodifiableMap(pathKeys);

        _uri = null;
        _hasUri = false;

        if (_baseUriTemplate != null && _pathKeys != null) {
            validatePathKeys();
        }

        _requestOptions = (requestOptions == null) ? Rest4JRequestOptions.DEFAULT_OPTIONS : requestOptions;
    }

    /**
     * Validates that a key is present on the request for a resource that requires one, and is absent otherwise.
     * @param key the key
     */
    protected void validateKeyPresence(Object key) {
        if (getResourceSpec().isKeylessResource()) {
            if (key != null) {
                throw new IllegalArgumentException("id is not allowed in this key-less resource request");
            }
        } else {
            if (key == null) {
                throw new IllegalArgumentException("id required to build this request");
            }
        }
    }

    /**
     * Converts the query params to read only.
     * @param queryParams the passed in query params
     * @return a read only version of the query params
     */
    private Map<String, Object> getReadOnlyQueryParams(Map<String, Object> queryParams) {
        for (Map.Entry<String, Object> entry : queryParams.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            queryParams.put(key, getReadOnly(value));
        }
        return Collections.unmodifiableMap(queryParams);
    }

    /**
     * Returns a read only version of {@code value}
     * @param value the object we want to get a read only version of
     * @return a read only version of {@code value}
     */
    private Object getReadOnly(Object value) {
        if (value == null) {
            return null;
        }

        if (value instanceof Object[]) {
            // array of non-primitives
            Object[] arr = (Object[]) value;
            List<Object> list = new ArrayList<Object>(arr.length);
            for (Object o : arr) {
                list.add(getReadOnly(o));
            }
            return Collections.unmodifiableList(list);
        } else if (value.getClass().isArray()) {
            // array of primitives
            int length = Array.getLength(value);
            List<Object> list = new ArrayList<Object>();
            for (int i = 0; i < length; i++) {
                list.add(Array.get(value, i));
            }
            return Collections.unmodifiableList(list);
        } else if (value instanceof ComplexResourceKey) {
            ((ComplexResourceKey) value).makeReadOnly();
            return value;
        } else if (value instanceof CompoundKey) {
            ((CompoundKey) value).makeReadOnly();
            return value;
        } else if (value instanceof DataTemplate) {
            Object data = ((DataTemplate) value).data();
            if (data instanceof DataComplex) {
                ((DataComplex) data).makeReadOnly();
            }
            // we don't try to make other types of data read only.
            return value;
        } else if (value instanceof Iterable) {
            List<Object> list = new ArrayList<Object>();
            for (Object o : (Iterable) value) {
                list.add(getReadOnly(o));
            }
            return Collections.unmodifiableList(list);
        }

        return value;
    }

    /**
     * Validates that all path keys in the URI template are present. If not, an {@link IllegalStateException} is thrown.
     */
    private void validatePathKeys() {
        UriTemplate template = new UriTemplate(getBaseUriTemplate());
        for (String key : template.getTemplateVariables()) {
            Object value = getPathKeys().get(key);
            if (value == null) {
                throw new IllegalStateException("Missing path key: " + key);
            }
        }
    }

    /**
     * @deprecated Requests are now built using a {@link com.ctriposs.rest4j.client.uribuilders.Rest4JUriBuilder}.
     * We do not recommend calling this method as URI generation is an expensive process and is dependent on the version
     * of Rest4J protocol being used.
     *
     * Consider using the {@link #toString()} method instead to meet your needs.
     *
     * If you must generate a URI, please use {@link com.ctriposs.rest4j.client.uribuilders.Rest4JUriBuilder#build()}
     *
     * @return the URI for this request.
     */
    @Deprecated
    public URI getUri() {
        if (_hasUri) {
            return _uri;
        } else {
            if (_uri == null) {
                // if someone calls this method w/o manually setting a URI in the constructor we will generate a URI using the
                // current default Rest4J version in the builders and cache it.
                _uri = Rest4JUriBuilderUtil.createUriBuilder(this).build();
            }
            return _uri;
        }
    }

    /**
     * THIS METHOD WILL BE REMOVED ONCE {@link #getUri()} has been removed.
     *
     * @return True if a legacy constructor was used to construct this {@link Request} object. False otherwise.
     */
    boolean hasUri() {
        return _hasUri;
    }

    public String getMethodName() {
        return _methodName;
    }

    /**
     * Returns the resource path parts as a list.
     *
     * The resource path of a root resource with a URI of "x/key1" has a resource is a list with one part: ["x"].
     *
     * The resource path of a sub-resource with a URI of "x/key1/y/key2" is a list with two parts: ["x", "y"].
     *
     * The resource path of a simple sub-resource with a URI of "x/key1/y/z/key2/t" is a list with
     * four parts: ["x", "y", "z", "t"].
     *
     * @return the resource path parts as a list.
     */
    @Deprecated
    public List<String> getResourcePath() {
        UriTemplate template = new UriTemplate(_baseUriTemplate);
        List<String> resourcePath = new ArrayList<String>(1);
        String[] pathParts = SLASH_PATTERN.split(template.createURI(Collections.<String, String>emptyMap()));

        for (String pathPart : pathParts) {
            if (!pathPart.equals("")) {
                resourcePath.add(pathPart);
            }
        }

        return resourcePath;
    }

    public ResourceMethod getMethod() {
        return _method;
    }

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

    /**
     * @deprecated Please use {@link #getInputRecord()} instead
     *
     * @return
     */
    @Deprecated
    public RecordTemplate getInput() {
        return _inputRecord;
    }

    public RecordTemplate getInputRecord() {
        return _inputRecord;
    }

    public RestResponseDecoder<T> getResponseDecoder() {
        return _decoder;
    }

    public ResourceSpec getResourceSpec() {
        return _resourceSpec;
    }

    public String getBaseUriTemplate() {
        return _baseUriTemplate;
    }

    public Map<String, Object> getPathKeys() {
        return _pathKeys;
    }

    /**
     * @see HttpMethod#isSafe()
     */
    public boolean isSafe() {
        return _method.getHttpMethod().isSafe();
    }

    /**
     * @see HttpMethod#isIdempotent()
     */
    public boolean isIdempotent() {
        return _method.getHttpMethod().isIdempotent();
    }

    /**
     * @deprecated Please use {@link #getQueryParamsObjects()} instead
     * @return
     */
    @Deprecated
    public DataMap getQueryParams() {
        return QueryParamsUtil.convertToDataMap(_queryParams);
    }

    public Map<String, Object> getQueryParamsObjects() {
        return _queryParams;
    }

    public Rest4JRequestOptions getRequestOptions() {
        return _requestOptions;
    }

    /**
     * This method is to be exposed in the extending classes when appropriate
     */
    protected Set<PathSpec> getFields() {
        @SuppressWarnings("unchecked")
        List<PathSpec> fieldsList = (List<PathSpec>) _queryParams.get(RestConstants.FIELDS_PARAM);
        if (fieldsList == null) {
            return Collections.emptySet();
        }
        return new HashSet<PathSpec>(fieldsList);
    }

    /**
     * Get the name of the service for this request
     * @return the service name for this request
     */
    String getServiceName() {
        if (_baseUriTemplate != null) {
            return _baseUriTemplate.split("/")[0];
        }
        return "";
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }

        Request<?> other = (Request<?>) obj;

        if (_hasUri && other._hasUri) {
            // both requests were constructed using the old constructor
            return areOldFieldsEqual(other) && _uri.equals(other._uri);
        }

        if (_hasUri || other._hasUri) {
            // if one of them was constructed using the new while the other was constructed using the old constructor we
            // assume that they are not equal.
            return false;
        }

        return areNewFieldsEqual(other);
    }

    /**
     * Checks if the old fields are equal
     *
     * @param other
     * @return
     */
    private boolean areOldFieldsEqual(Request<?> other) {
        if (_headers != null ? !_headers.equals(other._headers) : other._headers != null) {
            return false;
        }
        if (_inputRecord != null ? !_inputRecord.equals(other._inputRecord) : other._inputRecord != null) {
            return false;
        }
        if (_method != other._method) {
            return false;
        }
        return true;
    }

    /**
     * Checks if the new fields are equal
     *
     * @param other
     * @return
     */
    private boolean areNewFieldsEqual(Request<?> other) {
        if (!areOldFieldsEqual(other)) {
            return false;
        }
        if (_baseUriTemplate != null ? !_baseUriTemplate.equals(other._baseUriTemplate)
                : other._baseUriTemplate != null) {
            return false;
        }
        if (_pathKeys != null ? !_pathKeys.equals(other._pathKeys) : other._pathKeys != null) {
            return false;
        }
        if (_resourceSpec != null ? !_resourceSpec.equals(other._resourceSpec) : other._resourceSpec != null) {
            return false;
        }
        if (_queryParams != null ? !_queryParams.equals(other._queryParams) : other._queryParams != null) {
            return false;
        }
        if (_methodName != null ? !_methodName.equals(other._methodName) : other._methodName != null) {
            return false;
        }
        if (_requestOptions != null ? !_requestOptions.equals(other._requestOptions)
                : other._requestOptions != null) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int hashCode;
        if (_hasUri) {
            // request was constructed using an old constructor
            hashCode = _uri.hashCode();
            hashCode = (hashCode * 31) + oldHashCode();
        } else {
            hashCode = newHashCode();
        }
        return hashCode;
    }

    /**
     * Computes the hashCode using the old fields
     * @return
     */
    private int oldHashCode() {
        int result = _method.hashCode();
        result = 31 * result + (_inputRecord != null ? _inputRecord.hashCode() : 0);
        result = 31 * result + (_headers != null ? _headers.hashCode() : 0);
        return result;
    }

    /**
     * Computes the hashCode using the new fields
     * @return
     */
    private int newHashCode() {
        int hashCode = oldHashCode();
        hashCode = 31 * hashCode + (_baseUriTemplate != null ? _baseUriTemplate.hashCode() : 0);
        hashCode = 31 * hashCode + (_pathKeys != null ? _pathKeys.hashCode() : 0);
        hashCode = 31 * hashCode + (_resourceSpec != null ? _resourceSpec.hashCode() : 0);
        hashCode = 31 * hashCode + (_queryParams != null ? _queryParams.hashCode() : 0);
        hashCode = 31 * hashCode + (_methodName != null ? _methodName.hashCode() : 0);
        hashCode = 31 * hashCode + (_requestOptions != null ? _requestOptions.hashCode() : 0);
        return hashCode;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append(getClass().getName());
        sb.append("{_headers=").append(_headers);
        sb.append(", _input=").append(_inputRecord);
        sb.append(", _method=").append(_method);
        if (_hasUri) {
            // request was constructed using an old constructor
            sb.append(", _uri=").append(StringUtils.abbreviate(_uri.toString(), 256));
            sb.append("}");
        } else {
            sb.append(", _baseUriTemplate=").append(_baseUriTemplate);
            sb.append(", _methodName=").append(_methodName);
            sb.append(", _pathKeys=").append(_pathKeys);
            sb.append(", _queryParams=").append(_queryParams);
            sb.append(", _requestOptions=").append(_requestOptions);
            sb.append('}');
        }
        return sb.toString();
    }
}