/* Copyright (c) 2012 LinkedIn Corp. 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. */ /** * $Id: $ */ package com.linkedin.restli.internal.server; import com.linkedin.data.DataList; import com.linkedin.data.DataMap; import com.linkedin.data.template.StringArray; import com.linkedin.data.transform.filter.request.MaskTree; import com.linkedin.jersey.api.uri.UriComponent; import com.linkedin.r2.message.RequestContext; import com.linkedin.r2.message.rest.RestRequest; import com.linkedin.r2.message.rest.RestRequestBuilder; import com.linkedin.restli.common.HttpStatus; import com.linkedin.restli.common.ProtocolVersion; import com.linkedin.restli.common.RestConstants; import com.linkedin.restli.common.attachments.RestLiAttachmentReader; import com.linkedin.restli.internal.common.AllProtocolVersions; import com.linkedin.restli.internal.common.CookieUtil; import com.linkedin.restli.internal.common.PathSegment.PathSegmentSyntaxException; import com.linkedin.restli.internal.common.ProtocolVersionUtil; import com.linkedin.restli.internal.common.QueryParamsDataMap; import com.linkedin.restli.internal.common.URIParamUtils; import com.linkedin.restli.internal.server.util.ArgumentUtils; import com.linkedin.restli.internal.server.util.RestLiSyntaxException; import com.linkedin.restli.server.ProjectionMode; import com.linkedin.restli.server.RestLiResponseAttachments; import com.linkedin.restli.server.RestLiServiceException; import com.linkedin.restli.server.RoutingException; import java.net.HttpCookie; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; /** * @author Josh Walker * @version $Revision: $ */ public class ResourceContextImpl implements ServerResourceContext { private final MutablePathKeys _pathKeys; private final RestRequest _request; private final DataMap _parameters; private final Map<String, String> _requestHeaders; private final Map<String, String> _responseHeaders; private final List<HttpCookie> _requestCookies; private final List<HttpCookie> _responseCookies; private final Map<Object, RestLiServiceException> _batchKeyErrors; private final RequestContext _requestContext; private final ProtocolVersion _protocolVersion; private String _mimeType; //For root object entities private ProjectionMode _projectionMode; private MaskTree _projectionMask; //For the metadata inside of a CollectionResult private ProjectionMode _metadataProjectionMode; private MaskTree _metadataProjectionMask; //For paging. Note that there is no projection mode for paging (CollectionMetadata) because its fully automatic. //Client resource methods have the option of setting the total if they so desire, but restli will always //project CollectionMetadata if the client asks for it. //The paging projection mask is still available to both parties (the resource method and restli). private MaskTree _pagingProjectionMask; //For streaming attachments private final RestLiAttachmentReader _requestAttachmentReader; private final boolean _responseAttachmentsAllowed; private RestLiResponseAttachments _responseStreamingAttachments; /** * Default constructor. * * @throws RestLiSyntaxException cannot happen here */ public ResourceContextImpl() throws RestLiSyntaxException { this(new PathKeysImpl(), new RestRequestBuilder(URI.create("")) .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, AllProtocolVersions.LATEST_PROTOCOL_VERSION.toString()) .build(), new RequestContext()); } /** * Constructor. * * @param pathKeys path keys object * @param request request * @param requestContext context for the request * @throws RestLiSyntaxException if the syntax of query parameters in the request is * incorrect */ public ResourceContextImpl(final MutablePathKeys pathKeys, final RestRequest request, final RequestContext requestContext) throws RestLiSyntaxException { this(pathKeys, request, requestContext, false, null); } public ResourceContextImpl(final MutablePathKeys pathKeys, final RestRequest request, final RequestContext requestContext, final boolean responseAttachmentsAllowed, final RestLiAttachmentReader restLiAttachmentReader) throws RestLiSyntaxException { _pathKeys = pathKeys; _request = request; _requestHeaders = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); _requestHeaders.putAll(request.getHeaders()); _responseHeaders = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); _requestCookies = new ArrayList<HttpCookie>(CookieUtil.decodeCookies(_request.getCookies())); _responseCookies = new ArrayList<HttpCookie>(); _requestContext = requestContext; _responseAttachmentsAllowed = responseAttachmentsAllowed; _requestAttachmentReader = restLiAttachmentReader; _protocolVersion = ProtocolVersionUtil.extractProtocolVersion(request.getHeaders()); try { if (_protocolVersion.compareTo(AllProtocolVersions.RESTLI_PROTOCOL_2_0_0.getProtocolVersion()) >= 0) { Map<String, List<String>> queryParameters = UriComponent.decodeQuery(_request.getURI(), false); _parameters = URIParamUtils.parseUriParams(queryParameters); } else { Map<String, List<String>> queryParameters = ArgumentUtils.getQueryParameters(_request.getURI()); _parameters = QueryParamsDataMap.parseDataMapKeys(queryParameters); } } catch (PathSegmentSyntaxException e) { throw new RestLiSyntaxException("Invalid query parameters syntax: " + _request.getURI().toString(), e); } if (_parameters.containsKey(RestConstants.FIELDS_PARAM)) { _projectionMask = ArgumentUtils.parseProjectionParameter(ArgumentUtils.argumentAsString(getParameter(RestConstants.FIELDS_PARAM), RestConstants.FIELDS_PARAM)); } else { _projectionMask = null; } if (_parameters.containsKey(RestConstants.METADATA_FIELDS_PARAM)) { _metadataProjectionMask = ArgumentUtils.parseProjectionParameter(ArgumentUtils .argumentAsString(getParameter(RestConstants.METADATA_FIELDS_PARAM), RestConstants.METADATA_FIELDS_PARAM)); } else { _metadataProjectionMask = null; } if (_parameters.containsKey(RestConstants.PAGING_FIELDS_PARAM)) { _pagingProjectionMask = ArgumentUtils.parseProjectionParameter(ArgumentUtils .argumentAsString(getParameter(RestConstants.PAGING_FIELDS_PARAM), RestConstants.PAGING_FIELDS_PARAM)); } else { _pagingProjectionMask = null; } _batchKeyErrors = new HashMap<Object, RestLiServiceException>(); _projectionMode = ProjectionMode.getDefault(); _metadataProjectionMode = ProjectionMode.getDefault(); } @Override public DataMap getParameters() { return _parameters; } @Override public URI getRequestURI() { return _request.getURI(); } @Override public String getRequestActionName() { return ArgumentUtils.argumentAsString(getParameter(RestConstants.ACTION_PARAM), RestConstants.ACTION_PARAM); } @Override public String getRequestFinderName() { return ArgumentUtils.argumentAsString(getParameter(RestConstants.QUERY_TYPE_PARAM), RestConstants.QUERY_TYPE_PARAM); } @Override public String getRequestMethod() { return _request.getMethod(); } @Override public MutablePathKeys getPathKeys() { return _pathKeys; } @Override @Deprecated public RestRequest getRawRequest() { return _request; } @Override public MaskTree getProjectionMask() { return _projectionMask; } @Override public void setProjectionMask(MaskTree projectionMask) { _projectionMask = projectionMask; } @Override public MaskTree getMetadataProjectionMask() { return _metadataProjectionMask; } @Override public void setMetadataProjectionMask(MaskTree metadataProjectionMask) { _metadataProjectionMask = metadataProjectionMask; } @Override public MaskTree getPagingProjectionMask() { return _pagingProjectionMask; } @Override public void setPagingProjectionMask(MaskTree pagingProjectionMask) { _pagingProjectionMask = pagingProjectionMask; } @Override public String getParameter(final String key) { Object paramValueObj = _parameters.get(key); if (paramValueObj == null) { return null; } if (paramValueObj instanceof List) { List<?> paramValueList = (List<?>) paramValueObj; if (paramValueList.isEmpty()) { return null; } return paramValueList.get(0).toString(); } return paramValueObj.toString(); } @Override public Object getStructuredParameter(final String key) { return _parameters.get(key); } /* * This method is only applicable for "simple", i.e. non-RecordTemplate-based query * parameters. For backwards compatibility return List<String> but make sure the * parameter conforms to this type. */ @Override public List<String> getParameterValues(final String key) { Object paramObject = _parameters.get(key); if (paramObject == null) { return null; } if (paramObject instanceof String && _protocolVersion.compareTo(AllProtocolVersions.RESTLI_PROTOCOL_1_0_0.getProtocolVersion()) == 0) { return Collections.singletonList((String) paramObject); } if (!(paramObject instanceof DataList)) { throw new RoutingException("Invalid value type for parameter " + key, HttpStatus.S_400_BAD_REQUEST.getCode()); } return new StringArray((DataList) paramObject); } @Override public boolean hasParameter(final String key) { return _parameters.containsKey(key); } @Override public Map<String, String> getRequestHeaders() { return _requestHeaders; } @Override public List<HttpCookie> getRequestCookies() { return _requestCookies; } /** * @throws IllegalArgumentException when trying to set {@link RestConstants#HEADER_ID} or {@link RestConstants#HEADER_RESTLI_ID}. */ @Override public void setResponseHeader(final String name, final String value) { final String headerName; if (RestConstants.HEADER_ID.equals(name)) { headerName = RestConstants.HEADER_ID; } else if (RestConstants.HEADER_RESTLI_ID.equals(name)) { headerName = RestConstants.HEADER_RESTLI_ID; } else { headerName = null; } if (headerName != null) { throw new IllegalArgumentException("Illegal to set the \"" + headerName + "\" header. This header is reserved for the ID returned from create method on the resource."); } _responseHeaders.put(name, value); } @Override public void addResponseCookie(HttpCookie cookie) { if (cookie != null) _responseCookies.add(cookie); } @Override public RequestContext getRawRequestContext() { return _requestContext; } @Override public Map<String, String> getResponseHeaders() { return Collections.unmodifiableMap(_responseHeaders); } @Override public List<HttpCookie> getResponseCookies() { return _responseCookies; } @Override public Map<Object, RestLiServiceException> getBatchKeyErrors() { return _batchKeyErrors; } @Override public String getRestLiRequestMethod() { String headerValue = _request.getHeader(RestConstants.HEADER_RESTLI_REQUEST_METHOD); return headerValue == null ? "" : headerValue; } @Override public ProtocolVersion getRestliProtocolVersion() { return _protocolVersion; } @Override public ProjectionMode getProjectionMode() { return _projectionMode; } @Override public void setProjectionMode(ProjectionMode projectionMode) { _projectionMode = projectionMode; } @Override public ProjectionMode getMetadataProjectionMode() { return _metadataProjectionMode; } @Override public void setMetadataProjectionMode(ProjectionMode metadataProjectionMode) { _metadataProjectionMode = metadataProjectionMode; } @Override public void setResponseMimeType(String type) { _mimeType = type; } @Override public String getResponseMimeType() { return _mimeType; } @Override public boolean responseAttachmentsSupported() { return _responseAttachmentsAllowed; } @Override public RestLiAttachmentReader getRequestAttachmentReader() { return _requestAttachmentReader; } @Override public void setResponseAttachments(final RestLiResponseAttachments responseAttachments) throws IllegalStateException { if (!_responseAttachmentsAllowed) { throw new IllegalStateException("Response attachments can only be set if the client request indicates permissibility"); } _responseStreamingAttachments = responseAttachments; } @Override public RestLiResponseAttachments getResponseAttachments() { return _responseStreamingAttachments; } }