/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.management.internal.web.http; import java.net.URI; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.geode.internal.lang.Filter; import org.apache.geode.internal.lang.ObjectUtils; import org.apache.geode.internal.util.CollectionUtils; import org.apache.geode.management.internal.cli.multistep.CLIMultiStepHelper; import org.apache.geode.management.internal.web.domain.Link; import org.apache.geode.management.internal.web.util.UriUtils; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; import org.springframework.http.MediaType; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriTemplate; /** * The ClientHttpRequest class is an abstraction modeling an HTTP request sent by a client and * serves as the envelop encapsulating all the necessary information (headers, request parameters, * body, etc) to send the client's request using HTTP. * <p/> * The required information for an HTTP request comes from a combination of the Link class * containing the reference uniquely identifying the resource or location of where the request will * be sent, along with the HttpHeaders class capturing the headers for the request as well as the * generic container, HttpEntity to write the body of the request. * <p/> * This implementation of HttpRequest should not be confused with Spring's * org.springframework.http.client.ClientHttpRequest interface, which is often created by factory * using a specific HTTP client technology, like the Java HttpURLConnection or Apache's HTTP * components, and so on. * <p/> * * @see java.net.URI * @see org.apache.geode.management.internal.web.http.HttpHeader * @see org.apache.geode.management.internal.web.http.HttpMethod * @see org.apache.geode.management.internal.web.domain.Link * @see org.springframework.http.HttpEntity * @see org.springframework.http.HttpHeaders * @see org.springframework.http.HttpMethod * @see org.springframework.http.HttpRequest * @see org.springframework.http.MediaType * @see org.springframework.util.MultiValueMap * @see org.springframework.web.util.UriComponentsBuilder * @see org.springframework.web.util.UriTemplate * @since GemFire 8.0 */ @SuppressWarnings("unused") public class ClientHttpRequest implements HttpRequest { // the HTTP headers to be sent with the client's request message private final HttpHeaders requestHeaders = new HttpHeaders(); // the Link referencing the URI and method used with HTTP for the client's request private final Link link; // the mapping of request parameter name and values encoded for HTTP and sent with/in the client's // request message private final MultiValueMap<String, Object> requestParameters = new LinkedMultiValueMap<String, Object>(); // the content/media or payload for the body of the client's HTTP request private Object content; /** * Constructs an instance of the ClientHttpRequest class initialized with the specified Link * containing the URI and method for the client's HTTP request. * <p/> * * @param link the Link encapsulating the URI and method for the client's HTTP request. * @see org.apache.geode.management.internal.web.domain.Link */ public ClientHttpRequest(final Link link) { assert link != null : "The Link containing the URI and method for the client's HTTP request cannot be null!"; this.link = link; } /** * Gets the HTTP headers that will be sent in the client's HTTP request message. * <p/> * * @return the HTTP headers that will be sent in the client's HTTP request message. * @see org.springframework.http.HttpHeaders * @see org.springframework.http.HttpMessage#getHeaders() */ @Override public HttpHeaders getHeaders() { return requestHeaders; } /** * Gets the Link containing the URI and method used to send the client's HTTP request. * <p/> * * @return the Link encapsulating the URI and method for the client's HTTP request. * @see org.apache.geode.management.internal.web.domain.Link */ public final Link getLink() { return link; } /** * Gets the HTTP method indicating the operation to perform on the resource identified in the * client's HTTP request. This method converts GemFire's HttpMethod enumerated value from the Link * into a corresponding Spring HttpMethod enumerated value. * <p/> * * @return a Spring HttpMethod enumerated value indicating the operation to perform on the * resource identified in the client's HTTP request. * @see org.apache.geode.management.internal.web.http.HttpMethod * @see org.apache.geode.management.internal.web.domain.Link#getMethod() * @see org.springframework.http.HttpMethod * @see org.springframework.http.HttpRequest#getMethod() */ @Override public HttpMethod getMethod() { switch (getLink().getMethod()) { case DELETE: return HttpMethod.DELETE; case HEAD: return HttpMethod.HEAD; case OPTIONS: return HttpMethod.OPTIONS; case POST: return HttpMethod.POST; case PUT: return HttpMethod.PUT; case TRACE: return HttpMethod.TRACE; case GET: default: return HttpMethod.GET; } } /** * Determines whether this is an HTTP DELETE request. * <p/> * * @return a boolean value indicating if the HTTP method is DELETE. * @see #getMethod() * @see org.springframework.http.HttpMethod#DELETE */ public boolean isDelete() { return HttpMethod.DELETE.equals(getMethod()); } /** * Determines whether this is an HTTP GET request. * <p/> * * @return a boolean value indicating if the HTTP method is GET. * @see #getMethod() * @see org.springframework.http.HttpMethod#GET */ public boolean isGet() { return HttpMethod.GET.equals(getMethod()); } /** * Determines whether this is an HTTP POST request. * <p/> * * @return a boolean value indicating if the HTTP method is POST. * @see #getMethod() * @see org.springframework.http.HttpMethod#POST */ public boolean isPost() { return HttpMethod.POST.equals(getMethod()); } /** * Determines whether this is an HTTP PUT request. * <p/> * * @return a boolean value indicating if the HTTP method is PUT. * @see #getMethod() * @see org.springframework.http.HttpMethod#PUT */ public boolean isPut() { return HttpMethod.PUT.equals(getMethod()); } /** * Gets the request parameters that will be sent in the client's HTTP request message. * <p/> * * @return a MultiValueMap of request parameters and values that will be sent in the client's HTTP * request message. * @see org.springframework.util.MultiValueMap */ public MultiValueMap<String, Object> getParameters() { return requestParameters; } /** * Gets the path variables in the URI template. Note, this would be better placed in the Link * class, but Link cannot contain an Spring dependencies! * <p/> * * @return a List of Strings for each path variable in the URI template. * @see #getURI() * @see org.springframework.web.util.UriTemplate */ protected List<String> getPathVariables() { return Collections .unmodifiableList(new UriTemplate(UriUtils.decode(getURI().toString())).getVariableNames()); } /** * Gets the URI for the client's HTTP request. The URI may actually be an encoded URI template * containing path variables requiring expansion. * <p/> * * @return the URI of the resource targeted in the request by the client using HTTP. * @see java.net.URI * @see org.springframework.http.HttpRequest#getURI() */ @Override public URI getURI() { return getLink().getHref(); } /** * Gets the URL for the client's HTTP request. * <p/> * * @return a URL as a URI referring to the location of the resource requested by the client via * HTTP. * @see #getURL(java.util.Map) * @see java.net.URI */ public URI getURL() { return getURL(Collections.<String, Object>emptyMap()); } /** * Gets the URL for the client's HTTP request. * <p/> * * @param uriVariables a Map of URI path variables to values in order to expand the URI template * into a URI. * @return a URL as a URI referring to the location of the resource requested by the client via * HTTP. * @see #getURI() * @see java.net.URI * @see org.springframework.web.util.UriComponents * @see org.springframework.web.util.UriComponentsBuilder */ public URI getURL(final Map<String, ?> uriVariables) { final UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(UriUtils.decode(getURI().toString())); if (isGet() || isDelete()) { final List<String> pathVariables = getPathVariables(); // get query parameters to append to the URI/URL based on the request parameters that are not // path variables... final Map<String, List<Object>> queryParameters = CollectionUtils.removeKeys(new LinkedMultiValueMap<String, Object>(getParameters()), new Filter<Map.Entry<String, List<Object>>>() { @Override public boolean accept(final Map.Entry<String, List<Object>> entry) { // GEODE-1469: since stepArgs has json string in there, we will need to encode it // so that it won't interfere with the expand() call afterwards if (entry.getKey().contains(CLIMultiStepHelper.STEP_ARGS)) { List<Object> stepArgsList = entry.getValue(); if (stepArgsList != null) { String stepArgs = (String) stepArgsList.remove(0); stepArgsList.add(UriUtils.encode(stepArgs)); } } return !pathVariables.contains(entry.getKey()); } }); for (final String queryParameterName : queryParameters.keySet()) { uriBuilder.queryParam(queryParameterName, getParameters().get(queryParameterName).toArray()); } } return uriBuilder.build().expand(UriUtils.encode(new HashMap<String, Object>(uriVariables))) .encode().toUri(); } /** * Gets the HTTP request entity encapsulating the headers and body of the HTTP message. The body * of the HTTP request message will consist of an URL encoded application form (a mapping of * key-value pairs) for POST/PUT HTTP requests. * <p/> * * @return an HttpEntity with the headers and body for the HTTP request message. * @see #getParameters() * @see org.springframework.http.HttpEntity * @see org.springframework.http.HttpHeaders */ public HttpEntity<?> createRequestEntity() { if (isPost() || isPut()) { // NOTE HTTP request parameters take precedence over HTTP message body content/media if (!getParameters().isEmpty()) { getHeaders().setContentType(determineContentType(MediaType.APPLICATION_FORM_URLENCODED)); return new HttpEntity<MultiValueMap<String, Object>>(getParameters(), getHeaders()); } else { // NOTE the HTTP "Content-Type" header will be determined and set by the appropriate // HttpMessageConverter // based on the Class type of the "content". return new HttpEntity<Object>(getContent(), getHeaders()); } } else { return new HttpEntity<Object>(getHeaders()); } } /** * Tries to determine the content/media type of this HTTP request iff the HTTP "Content-Type" * header was not explicitly set by the user, otherwise the user provided value is used. If the * "Content-Type" HTTP header value is null, then the content/media/payload of this HTTP request * is inspected to determine the content type. * <p/> * The simplest evaluation sets the content type to "application/x-www-form-urlencoded" if this is * a POST or PUT HTTP request, unless any request parameter value is determined to have multiple * parts, the the content type will be "multipart/form-data". * <p/> * * @param defaultContentType the default content/media type to use when the content type cannot be * determined from this HTTP request. * @return a MediaType for the value of the HTTP Content-Type header as determined from this HTTP * request. * @see #getHeaders() * @see org.springframework.http.HttpHeaders#getContentType() * @see org.springframework.http.MediaType */ protected MediaType determineContentType(final MediaType defaultContentType) { MediaType contentType = getHeaders().getContentType(); // if the content type HTTP header was not explicitly set, try to determine the media type from // the content body // of the HTTP request if (contentType == null) { if (isPost() || isPut()) { OUT: for (final String name : getParameters().keySet()) { for (final Object value : getParameters().get(name)) { if (value != null && !(value instanceof String)) { contentType = MediaType.MULTIPART_FORM_DATA; break OUT; } } } // since this is a POST/PUT HTTP request, default the content/media type to // "application/x-www-form-urlencoded" contentType = ObjectUtils.defaultIfNull(contentType, MediaType.APPLICATION_FORM_URLENCODED); } else { // NOTE the "Content-Type" HTTP header is not applicable to GET/DELETE and other methods of // HTTP requests // since there is typically no content (media/payload/request body/etc) to send. Any request // parameters // are encoded in the URL as query parameters. } } return ObjectUtils.defaultIfNull(contentType, defaultContentType); } public Object getContent() { return content; } public void setContent(final Object content) { this.content = content; } /** * Adds 1 or more values for the specified HTTP header. * <p/> * * @param headerName a String specifying the name of the HTTP header. * @param headerValues the array of values to set for the HTTP header. * @see org.springframework.http.HttpHeaders#add(String, String) */ public void addHeaderValues(final String headerName, final String... headerValues) { if (headerValues != null) { for (final String headerValue : headerValues) { getHeaders().add(headerName, headerValue); } } } /** * Gets the first value for the specified HTTP header or null if the HTTP header is not set. * <p/> * * @param headerName a String specifying the name of the HTTP header. * @return the first value in the list of values for the HTTP header, or null if the HTTP header * is not set. * @see org.springframework.http.HttpHeaders#getFirst(String) */ public String getHeaderValue(final String headerName) { return getHeaders().getFirst(headerName); } /** * Gets all values for the specified HTTP header or an empty List if the HTTP header is not set. * <p/> * * @param headerName a String specifying the name of the HTTP header. * @return a list of String values for the specified HTTP header. * @see org.springframework.http.HttpHeaders#get(Object) */ public List<String> getHeaderValues(final String headerName) { return Collections.unmodifiableList(getHeaders().get(headerName)); } /** * Sets the specified HTTP header to the given value, overriding any previously set values for the * HTTP header. * <p/> * * @param headerName a String specifying the name of the HTTP header. * @param headerValue a String containing the value of the HTTP header. * @see org.springframework.http.HttpHeaders#set(String, String) */ public void setHeader(final String headerName, final String headerValue) { getHeaders().set(headerName, headerValue); } /** * Adds 1 or more parameter values to the HTTP request. * <p/> * * @param requestParameterName a String specifying the name of the HTTP request parameter. * @param requestParameterValues the array of values to set for the HTTP request parameter. * @see org.springframework.util.MultiValueMap#add(Object, Object) */ public void addParameterValues(final String requestParameterName, final Object... requestParameterValues) { if (requestParameterValues != null) { for (final Object requestParameterValue : requestParameterValues) { getParameters().add(requestParameterName, requestParameterValue); } } } /** * Gets the first value for the specified HTTP request parameter or null if the HTTP request * parameter is not set. * <p/> * * @param requestParameterName a String specifying the name of the HTTP request parameter. * @return the first value in the list of values for the HTTP request parameter, or null if the * HTTP request parameter is not set. * @see org.springframework.util.MultiValueMap#getFirst(Object) */ public Object getParameterValue(final String requestParameterName) { return getParameters().getFirst(requestParameterName); } /** * Gets all values for the specified HTTP request parameter or an empty List if the HTTP request * parameter is not set. * <p/> * * @param requestParameterName a String specifying the name of the HTTP request parameter. * @return a list of String values for the specified HTTP request parameter. * @see org.springframework.util.MultiValueMap#get(Object) */ public List<Object> getParameterValues(final String requestParameterName) { return Collections.unmodifiableList(getParameters().get(requestParameterName)); } /** * Sets the specified HTTP request parameter to the given value, overriding any previously set * values for the HTTP request parameter. * <p/> * * @param name a String specifying the name of the HTTP request parameter. * @param value a String containing the value of the HTTP request parameter. * @see org.springframework.util.MultiValueMap#set(Object, Object) */ public void setParameter(final String name, final Object value) { getParameters().set(name, value); } }