Rename modules {org.springframework.*=>spring-*}
This renaming more intuitively expresses the relationship between
subprojects and the JAR artifacts they produce.
Tracking history across these renames is possible, but it requires
use of the --follow flag to `git log`, for example
$ git log spring-aop/src/main/java/org/springframework/aop/Advisor.java
will show history up until the renaming event, where
$ git log --follow spring-aop/src/main/java/org/springframework/aop/Advisor.java
will show history for all changes to the file, before and after the
renaming.
See http://chrisbeams.com/git-diff-across-renamed-directories
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http;
|
||||
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Represents an HTTP request or response entity, consisting of headers and body.
|
||||
*
|
||||
* <p>Typically used in combination with the {@link org.springframework.web.client.RestTemplate RestTemplate}, like so:
|
||||
* <pre class="code">
|
||||
* HttpHeaders headers = new HttpHeaders();
|
||||
* headers.setContentType(MediaType.TEXT_PLAIN);
|
||||
* HttpEntity<String> entity = new HttpEntity<String>(helloWorld, headers);
|
||||
* URI location = template.postForLocation("http://example.com", entity);
|
||||
* </pre>
|
||||
* or
|
||||
* <pre class="code">
|
||||
* HttpEntity<String> entity = template.getForEntity("http://example.com", String.class);
|
||||
* String body = entity.getBody();
|
||||
* MediaType contentType = entity.getHeaders().getContentType();
|
||||
* </pre>
|
||||
* Can also be used in Spring MVC, as a return value from a @Controller method:
|
||||
* <pre class="code">
|
||||
* @RequestMapping("/handle")
|
||||
* public HttpEntity<String> handle() {
|
||||
* HttpHeaders responseHeaders = new HttpHeaders();
|
||||
* responseHeaders.set("MyResponseHeader", "MyValue");
|
||||
* return new HttpEntity<String>("Hello World", responseHeaders);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0.2
|
||||
* @see org.springframework.web.client.RestTemplate
|
||||
* @see #getBody()
|
||||
* @see #getHeaders()
|
||||
*/
|
||||
public class HttpEntity<T> {
|
||||
|
||||
/**
|
||||
* The empty {@code HttpEntity}, with no body or headers.
|
||||
*/
|
||||
public static final HttpEntity EMPTY = new HttpEntity();
|
||||
|
||||
|
||||
private final HttpHeaders headers;
|
||||
|
||||
private final T body;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new, empty {@code HttpEntity}.
|
||||
*/
|
||||
protected HttpEntity() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code HttpEntity} with the given body and no headers.
|
||||
* @param body the entity body
|
||||
*/
|
||||
public HttpEntity(T body) {
|
||||
this(body, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code HttpEntity} with the given headers and no body.
|
||||
* @param headers the entity headers
|
||||
*/
|
||||
public HttpEntity(MultiValueMap<String, String> headers) {
|
||||
this(null, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code HttpEntity} with the given body and headers.
|
||||
* @param body the entity body
|
||||
* @param headers the entity headers
|
||||
*/
|
||||
public HttpEntity(T body, MultiValueMap<String, String> headers) {
|
||||
this.body = body;
|
||||
HttpHeaders tempHeaders = new HttpHeaders();
|
||||
if (headers != null) {
|
||||
tempHeaders.putAll(headers);
|
||||
}
|
||||
this.headers = HttpHeaders.readOnlyHttpHeaders(tempHeaders);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the headers of this entity.
|
||||
*/
|
||||
public HttpHeaders getHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the body of this entity.
|
||||
*/
|
||||
public T getBody() {
|
||||
return this.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this entity has a body.
|
||||
*/
|
||||
public boolean hasBody() {
|
||||
return (this.body != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder("<");
|
||||
if (body != null) {
|
||||
builder.append(body);
|
||||
if (headers != null) {
|
||||
builder.append(',');
|
||||
}
|
||||
}
|
||||
if (headers != null) {
|
||||
builder.append(headers);
|
||||
}
|
||||
builder.append('>');
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,618 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Represents HTTP request and response headers, mapping string header names to list of string values.
|
||||
*
|
||||
* <p>In addition to the normal methods defined by {@link Map}, this class offers the following convenience methods:
|
||||
* <ul>
|
||||
* <li>{@link #getFirst(String)} returns the first value associated with a given header name</li>
|
||||
* <li>{@link #add(String, String)} adds a header value to the list of values for a header name</li>
|
||||
* <li>{@link #set(String, String)} sets the header value to a single string value</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Inspired by {@link com.sun.net.httpserver.Headers}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public class HttpHeaders implements MultiValueMap<String, String> {
|
||||
|
||||
private static final String ACCEPT = "Accept";
|
||||
|
||||
private static final String ACCEPT_CHARSET = "Accept-Charset";
|
||||
|
||||
private static final String ALLOW = "Allow";
|
||||
|
||||
private static final String CACHE_CONTROL = "Cache-Control";
|
||||
|
||||
private static final String CONTENT_DISPOSITION = "Content-Disposition";
|
||||
|
||||
private static final String CONTENT_LENGTH = "Content-Length";
|
||||
|
||||
private static final String CONTENT_TYPE = "Content-Type";
|
||||
|
||||
private static final String DATE = "Date";
|
||||
|
||||
private static final String ETAG = "ETag";
|
||||
|
||||
private static final String EXPIRES = "Expires";
|
||||
|
||||
private static final String IF_MODIFIED_SINCE = "If-Modified-Since";
|
||||
|
||||
private static final String IF_NONE_MATCH = "If-None-Match";
|
||||
|
||||
private static final String LAST_MODIFIED = "Last-Modified";
|
||||
|
||||
private static final String LOCATION = "Location";
|
||||
|
||||
private static final String PRAGMA = "Pragma";
|
||||
|
||||
|
||||
private static final String[] DATE_FORMATS = new String[] {
|
||||
"EEE, dd MMM yyyy HH:mm:ss zzz",
|
||||
"EEE, dd-MMM-yy HH:mm:ss zzz",
|
||||
"EEE MMM dd HH:mm:ss yyyy"
|
||||
};
|
||||
|
||||
private static TimeZone GMT = TimeZone.getTimeZone("GMT");
|
||||
|
||||
private final Map<String, List<String>> headers;
|
||||
|
||||
|
||||
/**
|
||||
* Private constructor that can create read-only {@code HttpHeader} instances.
|
||||
*/
|
||||
private HttpHeaders(Map<String, List<String>> headers, boolean readOnly) {
|
||||
Assert.notNull(headers, "'headers' must not be null");
|
||||
if (readOnly) {
|
||||
Map<String, List<String>> map =
|
||||
new LinkedCaseInsensitiveMap<List<String>>(headers.size(), Locale.ENGLISH);
|
||||
for (Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
List<String> values = Collections.unmodifiableList(entry.getValue());
|
||||
map.put(entry.getKey(), values);
|
||||
}
|
||||
this.headers = Collections.unmodifiableMap(map);
|
||||
}
|
||||
else {
|
||||
this.headers = headers;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new, empty instance of the {@code HttpHeaders} object.
|
||||
*/
|
||||
public HttpHeaders() {
|
||||
this(new LinkedCaseInsensitiveMap<List<String>>(8, Locale.ENGLISH), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code HttpHeaders} object that can only be read, not written to.
|
||||
*/
|
||||
public static HttpHeaders readOnlyHttpHeaders(HttpHeaders headers) {
|
||||
return new HttpHeaders(headers, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of acceptable {@linkplain MediaType media types}, as specified by the {@code Accept} header.
|
||||
* @param acceptableMediaTypes the acceptable media types
|
||||
*/
|
||||
public void setAccept(List<MediaType> acceptableMediaTypes) {
|
||||
set(ACCEPT, MediaType.toString(acceptableMediaTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of acceptable {@linkplain MediaType media types}, as specified by the {@code Accept} header.
|
||||
* <p>Returns an empty list when the acceptable media types are unspecified.
|
||||
* @return the acceptable media types
|
||||
*/
|
||||
public List<MediaType> getAccept() {
|
||||
String value = getFirst(ACCEPT);
|
||||
return (value != null ? MediaType.parseMediaTypes(value) : Collections.<MediaType>emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of acceptable {@linkplain Charset charsets}, as specified by the {@code Accept-Charset} header.
|
||||
* @param acceptableCharsets the acceptable charsets
|
||||
*/
|
||||
public void setAcceptCharset(List<Charset> acceptableCharsets) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Iterator<Charset> iterator = acceptableCharsets.iterator(); iterator.hasNext();) {
|
||||
Charset charset = iterator.next();
|
||||
builder.append(charset.name().toLowerCase(Locale.ENGLISH));
|
||||
if (iterator.hasNext()) {
|
||||
builder.append(", ");
|
||||
}
|
||||
}
|
||||
set(ACCEPT_CHARSET, builder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of acceptable {@linkplain Charset charsets}, as specified by the {@code Accept-Charset}
|
||||
* header.
|
||||
* @return the acceptable charsets
|
||||
*/
|
||||
public List<Charset> getAcceptCharset() {
|
||||
List<Charset> result = new ArrayList<Charset>();
|
||||
String value = getFirst(ACCEPT_CHARSET);
|
||||
if (value != null) {
|
||||
String[] tokens = value.split(",\\s*");
|
||||
for (String token : tokens) {
|
||||
int paramIdx = token.indexOf(';');
|
||||
String charsetName;
|
||||
if (paramIdx == -1) {
|
||||
charsetName = token;
|
||||
}
|
||||
else {
|
||||
charsetName = token.substring(0, paramIdx);
|
||||
}
|
||||
if (!charsetName.equals("*")) {
|
||||
result.add(Charset.forName(charsetName));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the set of allowed {@link HttpMethod HTTP methods}, as specified by the {@code Allow} header.
|
||||
* @param allowedMethods the allowed methods
|
||||
*/
|
||||
public void setAllow(Set<HttpMethod> allowedMethods) {
|
||||
set(ALLOW, StringUtils.collectionToCommaDelimitedString(allowedMethods));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of allowed {@link HttpMethod HTTP methods}, as specified by the {@code Allow} header.
|
||||
* <p>Returns an empty set when the allowed methods are unspecified.
|
||||
* @return the allowed methods
|
||||
*/
|
||||
public Set<HttpMethod> getAllow() {
|
||||
String value = getFirst(ALLOW);
|
||||
if (value != null) {
|
||||
List<HttpMethod> allowedMethod = new ArrayList<HttpMethod>(5);
|
||||
String[] tokens = value.split(",\\s*");
|
||||
for (String token : tokens) {
|
||||
allowedMethod.add(HttpMethod.valueOf(token));
|
||||
}
|
||||
return EnumSet.copyOf(allowedMethod);
|
||||
}
|
||||
else {
|
||||
return EnumSet.noneOf(HttpMethod.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the (new) value of the {@code Cache-Control} header.
|
||||
* @param cacheControl the value of the header
|
||||
*/
|
||||
public void setCacheControl(String cacheControl) {
|
||||
set(CACHE_CONTROL, cacheControl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code Cache-Control} header.
|
||||
* @return the value of the header
|
||||
*/
|
||||
public String getCacheControl() {
|
||||
return getFirst(CACHE_CONTROL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the (new) value of the {@code Content-Disposition} header for {@code form-data}.
|
||||
* @param name the control name
|
||||
* @param filename the filename, may be {@code null}
|
||||
*/
|
||||
public void setContentDispositionFormData(String name, String filename) {
|
||||
Assert.notNull(name, "'name' must not be null");
|
||||
StringBuilder builder = new StringBuilder("form-data; name=\"");
|
||||
builder.append(name).append('\"');
|
||||
if (filename != null) {
|
||||
builder.append("; filename=\"");
|
||||
builder.append(filename).append('\"');
|
||||
}
|
||||
set(CONTENT_DISPOSITION, builder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the length of the body in bytes, as specified by the {@code Content-Length} header.
|
||||
* @param contentLength the content length
|
||||
*/
|
||||
public void setContentLength(long contentLength) {
|
||||
set(CONTENT_LENGTH, Long.toString(contentLength));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the length of the body in bytes, as specified by the {@code Content-Length} header.
|
||||
* <p>Returns -1 when the content-length is unknown.
|
||||
* @return the content length
|
||||
*/
|
||||
public long getContentLength() {
|
||||
String value = getFirst(CONTENT_LENGTH);
|
||||
return (value != null ? Long.parseLong(value) : -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@linkplain MediaType media type} of the body, as specified by the {@code Content-Type} header.
|
||||
* @param mediaType the media type
|
||||
*/
|
||||
public void setContentType(MediaType mediaType) {
|
||||
Assert.isTrue(!mediaType.isWildcardType(), "'Content-Type' cannot contain wildcard type '*'");
|
||||
Assert.isTrue(!mediaType.isWildcardSubtype(), "'Content-Type' cannot contain wildcard subtype '*'");
|
||||
set(CONTENT_TYPE, mediaType.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@linkplain MediaType media type} of the body, as specified by the {@code Content-Type} header.
|
||||
* <p>Returns {@code null} when the content-type is unknown.
|
||||
* @return the content type
|
||||
*/
|
||||
public MediaType getContentType() {
|
||||
String value = getFirst(CONTENT_TYPE);
|
||||
return (value != null ? MediaType.parseMediaType(value) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the date and time at which the message was created, as specified by the {@code Date} header.
|
||||
* <p>The date should be specified as the number of milliseconds since January 1, 1970 GMT.
|
||||
* @param date the date
|
||||
*/
|
||||
public void setDate(long date) {
|
||||
setDate(DATE, date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date and time at which the message was created, as specified by the {@code Date} header.
|
||||
* <p>The date is returned as the number of milliseconds since January 1, 1970 GMT. Returns -1 when the date is unknown.
|
||||
* @return the creation date/time
|
||||
* @throws IllegalArgumentException if the value can't be converted to a date
|
||||
*/
|
||||
public long getDate() {
|
||||
return getFirstDate(DATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the (new) entity tag of the body, as specified by the {@code ETag} header.
|
||||
* @param eTag the new entity tag
|
||||
*/
|
||||
public void setETag(String eTag) {
|
||||
if (eTag != null) {
|
||||
Assert.isTrue(eTag.startsWith("\"") || eTag.startsWith("W/"), "Invalid eTag, does not start with W/ or \"");
|
||||
Assert.isTrue(eTag.endsWith("\""), "Invalid eTag, does not end with \"");
|
||||
}
|
||||
set(ETAG, eTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entity tag of the body, as specified by the {@code ETag} header.
|
||||
* @return the entity tag
|
||||
*/
|
||||
public String getETag() {
|
||||
return getFirst(ETAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the date and time at which the message is no longer valid, as specified by the {@code Expires} header.
|
||||
* <p>The date should be specified as the number of milliseconds since January 1, 1970 GMT.
|
||||
* @param expires the new expires header value
|
||||
*/
|
||||
public void setExpires(long expires) {
|
||||
setDate(EXPIRES, expires);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date and time at which the message is no longer valid, as specified by the {@code Expires} header.
|
||||
* <p>The date is returned as the number of milliseconds since January 1, 1970 GMT. Returns -1 when the date is unknown.
|
||||
* @return the expires value
|
||||
*/
|
||||
public long getExpires() {
|
||||
return getFirstDate(EXPIRES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the (new) value of the {@code If-Modified-Since} header.
|
||||
* <p>The date should be specified as the number of milliseconds since January 1, 1970 GMT.
|
||||
* @param ifModifiedSince the new value of the header
|
||||
*/
|
||||
public void setIfModifiedSince(long ifModifiedSince) {
|
||||
setDate(IF_MODIFIED_SINCE, ifModifiedSince);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code IfModifiedSince} header.
|
||||
* <p>The date is returned as the number of milliseconds since January 1, 1970 GMT. Returns -1 when the date is unknown.
|
||||
* @return the header value
|
||||
*/
|
||||
public long getIfNotModifiedSince() {
|
||||
return getFirstDate(IF_MODIFIED_SINCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the (new) value of the {@code If-None-Match} header.
|
||||
* @param ifNoneMatch the new value of the header
|
||||
*/
|
||||
public void setIfNoneMatch(String ifNoneMatch) {
|
||||
set(IF_NONE_MATCH, ifNoneMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the (new) values of the {@code If-None-Match} header.
|
||||
* @param ifNoneMatchList the new value of the header
|
||||
*/
|
||||
public void setIfNoneMatch(List<String> ifNoneMatchList) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Iterator<String> iterator = ifNoneMatchList.iterator(); iterator.hasNext();) {
|
||||
String ifNoneMatch = iterator.next();
|
||||
builder.append(ifNoneMatch);
|
||||
if (iterator.hasNext()) {
|
||||
builder.append(", ");
|
||||
}
|
||||
}
|
||||
set(IF_NONE_MATCH, builder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code If-None-Match} header.
|
||||
* @return the header value
|
||||
*/
|
||||
public List<String> getIfNoneMatch() {
|
||||
List<String> result = new ArrayList<String>();
|
||||
|
||||
String value = getFirst(IF_NONE_MATCH);
|
||||
if (value != null) {
|
||||
String[] tokens = value.split(",\\s*");
|
||||
for (String token : tokens) {
|
||||
result.add(token);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time the resource was last changed, as specified by the {@code Last-Modified} header.
|
||||
* <p>The date should be specified as the number of milliseconds since January 1, 1970 GMT.
|
||||
* @param lastModified the last modified date
|
||||
*/
|
||||
public void setLastModified(long lastModified) {
|
||||
setDate(LAST_MODIFIED, lastModified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time the resource was last changed, as specified by the {@code Last-Modified} header.
|
||||
* <p>The date is returned as the number of milliseconds since January 1, 1970 GMT. Returns -1 when the date is unknown.
|
||||
* @return the last modified date
|
||||
*/
|
||||
public long getLastModified() {
|
||||
return getFirstDate(LAST_MODIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the (new) location of a resource, as specified by the {@code Location} header.
|
||||
* @param location the location
|
||||
*/
|
||||
public void setLocation(URI location) {
|
||||
set(LOCATION, location.toASCIIString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the (new) location of a resource, as specified by the {@code Location} header.
|
||||
* <p>Returns {@code null} when the location is unknown.
|
||||
* @return the location
|
||||
*/
|
||||
public URI getLocation() {
|
||||
String value = getFirst(LOCATION);
|
||||
return (value != null ? URI.create(value) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the (new) value of the {@code Pragma} header.
|
||||
* @param pragma the value of the header
|
||||
*/
|
||||
public void setPragma(String pragma) {
|
||||
set(PRAGMA, pragma);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code Pragma} header.
|
||||
* @return the value of the header
|
||||
*/
|
||||
public String getPragma() {
|
||||
return getFirst(PRAGMA);
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
|
||||
private long getFirstDate(String headerName) {
|
||||
String headerValue = getFirst(headerName);
|
||||
if (headerValue == null) {
|
||||
return -1;
|
||||
}
|
||||
for (String dateFormat : DATE_FORMATS) {
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat, Locale.US);
|
||||
simpleDateFormat.setTimeZone(GMT);
|
||||
try {
|
||||
return simpleDateFormat.parse(headerValue).getTime();
|
||||
}
|
||||
catch (ParseException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot parse date value \"" + headerValue +
|
||||
"\" for \"" + headerName + "\" header");
|
||||
}
|
||||
|
||||
private void setDate(String headerName, long date) {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMATS[0], Locale.US);
|
||||
dateFormat.setTimeZone(GMT);
|
||||
set(headerName, dateFormat.format(new Date(date)));
|
||||
}
|
||||
|
||||
// Single string methods
|
||||
|
||||
/**
|
||||
* Return the first header value for the given header name, if any.
|
||||
* @param headerName the header name
|
||||
* @return the first header value; or {@code null}
|
||||
*/
|
||||
public String getFirst(String headerName) {
|
||||
List<String> headerValues = headers.get(headerName);
|
||||
return headerValues != null ? headerValues.get(0) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given, single header value under the given name.
|
||||
* @param headerName the header name
|
||||
* @param headerValue the header value
|
||||
* @throws UnsupportedOperationException if adding headers is not supported
|
||||
* @see #put(String, List)
|
||||
* @see #set(String, String)
|
||||
*/
|
||||
public void add(String headerName, String headerValue) {
|
||||
List<String> headerValues = headers.get(headerName);
|
||||
if (headerValues == null) {
|
||||
headerValues = new LinkedList<String>();
|
||||
this.headers.put(headerName, headerValues);
|
||||
}
|
||||
headerValues.add(headerValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given, single header value under the given name.
|
||||
* @param headerName the header name
|
||||
* @param headerValue the header value
|
||||
* @throws UnsupportedOperationException if adding headers is not supported
|
||||
* @see #put(String, List)
|
||||
* @see #add(String, String)
|
||||
*/
|
||||
public void set(String headerName, String headerValue) {
|
||||
List<String> headerValues = new LinkedList<String>();
|
||||
headerValues.add(headerValue);
|
||||
headers.put(headerName, headerValues);
|
||||
}
|
||||
|
||||
public void setAll(Map<String, String> values) {
|
||||
for (Entry<String, String> entry : values.entrySet()) {
|
||||
set(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, String> toSingleValueMap() {
|
||||
LinkedHashMap<String, String> singleValueMap = new LinkedHashMap<String,String>(this.headers.size());
|
||||
for (Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
singleValueMap.put(entry.getKey(), entry.getValue().get(0));
|
||||
}
|
||||
return singleValueMap;
|
||||
}
|
||||
|
||||
// Map implementation
|
||||
|
||||
public int size() {
|
||||
return this.headers.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return this.headers.isEmpty();
|
||||
}
|
||||
|
||||
public boolean containsKey(Object key) {
|
||||
return this.headers.containsKey(key);
|
||||
}
|
||||
|
||||
public boolean containsValue(Object value) {
|
||||
return this.headers.containsValue(value);
|
||||
}
|
||||
|
||||
public List<String> get(Object key) {
|
||||
return this.headers.get(key);
|
||||
}
|
||||
|
||||
public List<String> put(String key, List<String> value) {
|
||||
return this.headers.put(key, value);
|
||||
}
|
||||
|
||||
public List<String> remove(Object key) {
|
||||
return this.headers.remove(key);
|
||||
}
|
||||
|
||||
public void putAll(Map<? extends String, ? extends List<String>> m) {
|
||||
this.headers.putAll(m);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.headers.clear();
|
||||
}
|
||||
|
||||
public Set<String> keySet() {
|
||||
return this.headers.keySet();
|
||||
}
|
||||
|
||||
public Collection<List<String>> values() {
|
||||
return this.headers.values();
|
||||
}
|
||||
|
||||
public Set<Entry<String, List<String>>> entrySet() {
|
||||
return this.headers.entrySet();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof HttpHeaders)) {
|
||||
return false;
|
||||
}
|
||||
HttpHeaders otherHeaders = (HttpHeaders) other;
|
||||
return this.headers.equals(otherHeaders.headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.headers.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.headers.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Represents an HTTP input message, consisting of {@linkplain #getHeaders() headers}
|
||||
* and a readable {@linkplain #getBody() body}.
|
||||
*
|
||||
* <p>Typically implemented by an HTTP request on the server-side, or a response on the client-side.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface HttpInputMessage extends HttpMessage {
|
||||
|
||||
/**
|
||||
* Return the body of the message as an input stream.
|
||||
* @return the input stream body
|
||||
* @throws IOException in case of I/O Errors
|
||||
*/
|
||||
InputStream getBody() throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http;
|
||||
|
||||
/**
|
||||
* Represents the base interface for HTTP request and response messages. Consists of {@link HttpHeaders}, retrievable
|
||||
* via {@link #getHeaders()}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface HttpMessage {
|
||||
|
||||
/**
|
||||
* Return the headers of this message.
|
||||
* @return a corresponding HttpHeaders object
|
||||
*/
|
||||
HttpHeaders getHeaders();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http;
|
||||
|
||||
/**
|
||||
* Java 5 enumeration of HTTP request methods. Intended for use
|
||||
* with {@link org.springframework.http.client.ClientHttpRequest}
|
||||
* and {@link org.springframework.web.client.RestTemplate}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public enum HttpMethod {
|
||||
|
||||
GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Represents an HTTP output message, consisting of {@linkplain #getHeaders() headers}
|
||||
* and a writable {@linkplain #getBody() body}.
|
||||
*
|
||||
* <p>Typically implemented by an HTTP request on the client-side, or a response on the server-side.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface HttpOutputMessage extends HttpMessage {
|
||||
|
||||
/**
|
||||
* Return the body of the message as an output stream.
|
||||
* @return the output stream body
|
||||
* @throws IOException in case of I/O Errors
|
||||
*/
|
||||
OutputStream getBody() throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Represents an HTTP request message, consisting of {@linkplain #getMethod() method}
|
||||
* and {@linkplain #getURI() uri}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
*/
|
||||
public interface HttpRequest extends HttpMessage {
|
||||
|
||||
/**
|
||||
* Return the HTTP method of the request.
|
||||
* @return the HTTP method as an HttpMethod enum value
|
||||
*/
|
||||
HttpMethod getMethod();
|
||||
|
||||
/**
|
||||
* Return the URI of the request.
|
||||
* @return the URI of the request
|
||||
*/
|
||||
URI getURI();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http;
|
||||
|
||||
/**
|
||||
* Java 5 enumeration of HTTP status codes.
|
||||
*
|
||||
* <p>The HTTP status code series can be retrieved via {@link #series()}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @see HttpStatus.Series
|
||||
* @see <a href="http://www.iana.org/assignments/http-status-codes">HTTP Status Code Registry</a>
|
||||
* @see <a href="http://en.wikipedia.org/wiki/List_of_HTTP_status_codes">List of HTTP status codes - Wikipedia</a>
|
||||
*/
|
||||
public enum HttpStatus {
|
||||
|
||||
// 1xx Informational
|
||||
|
||||
/**
|
||||
* {@code 100 Continue}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.1.1">HTTP/1.1</a>
|
||||
*/
|
||||
CONTINUE(100, "Continue"),
|
||||
/**
|
||||
* {@code 101 Switching Protocols}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.1.2">HTTP/1.1</a>
|
||||
*/
|
||||
SWITCHING_PROTOCOLS(101, "Switching Protocols"),
|
||||
/**
|
||||
* {@code 102 Processing}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2518#section-10.1">WebDAV</a>
|
||||
*/
|
||||
PROCESSING(102, "Processing"),
|
||||
/**
|
||||
* {@code 103 Checkpoint}.
|
||||
* @see <a href="http://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal">A proposal for supporting
|
||||
* resumable POST/PUT HTTP requests in HTTP/1.0</a>
|
||||
*/
|
||||
CHECKPOINT(103, "Checkpoint"),
|
||||
|
||||
// 2xx Success
|
||||
|
||||
/**
|
||||
* {@code 200 OK}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.1">HTTP/1.1</a>
|
||||
*/
|
||||
OK(200, "OK"),
|
||||
/**
|
||||
* {@code 201 Created}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.2">HTTP/1.1</a>
|
||||
*/
|
||||
CREATED(201, "Created"),
|
||||
/**
|
||||
* {@code 202 Accepted}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.3">HTTP/1.1</a>
|
||||
*/
|
||||
ACCEPTED(202, "Accepted"),
|
||||
/**
|
||||
* {@code 203 Non-Authoritative Information}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.4">HTTP/1.1</a>
|
||||
*/
|
||||
NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"),
|
||||
/**
|
||||
* {@code 204 No Content}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.5">HTTP/1.1</a>
|
||||
*/
|
||||
NO_CONTENT(204, "No Content"),
|
||||
/**
|
||||
* {@code 205 Reset Content}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.6">HTTP/1.1</a>
|
||||
*/
|
||||
RESET_CONTENT(205, "Reset Content"),
|
||||
/**
|
||||
* {@code 206 Partial Content}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.7">HTTP/1.1</a>
|
||||
*/
|
||||
PARTIAL_CONTENT(206, "Partial Content"),
|
||||
/**
|
||||
* {@code 207 Multi-Status}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc4918#section-13">WebDAV</a>
|
||||
*/
|
||||
MULTI_STATUS(207, "Multi-Status"),
|
||||
/**
|
||||
* {@code 208 Already Reported}.
|
||||
* @see <a href="http://tools.ietf.org/html/draft-ietf-webdav-bind-27#section-7.1">WebDAV Binding Extensions</a>
|
||||
*/
|
||||
ALREADY_REPORTED(208, "Already Reported"),
|
||||
/**
|
||||
* {@code 226 IM Used}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc3229#section-10.4.1">Delta encoding in HTTP</a>
|
||||
*/
|
||||
IM_USED(226, "IM Used"),
|
||||
|
||||
// 3xx Redirection
|
||||
|
||||
/**
|
||||
* {@code 300 Multiple Choices}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.1">HTTP/1.1</a>
|
||||
*/
|
||||
MULTIPLE_CHOICES(300, "Multiple Choices"),
|
||||
/**
|
||||
* {@code 301 Moved Permanently}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.2">HTTP/1.1</a>
|
||||
*/
|
||||
MOVED_PERMANENTLY(301, "Moved Permanently"),
|
||||
/**
|
||||
* {@code 302 Found}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.3">HTTP/1.1</a>
|
||||
*/
|
||||
FOUND(302, "Found"),
|
||||
/**
|
||||
* {@code 302 Moved Temporarily}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc1945#section-9.3">HTTP/1.0</a>
|
||||
*/
|
||||
MOVED_TEMPORARILY(302, "Moved Temporarily"),
|
||||
/**
|
||||
* {@code 303 See Other}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.4">HTTP/1.1</a>
|
||||
*/
|
||||
SEE_OTHER(303, "See Other"),
|
||||
/**
|
||||
* {@code 304 Not Modified}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.5">HTTP/1.1</a>
|
||||
*/
|
||||
NOT_MODIFIED(304, "Not Modified"),
|
||||
/**
|
||||
* {@code 305 Use Proxy}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.6">HTTP/1.1</a>
|
||||
*/
|
||||
USE_PROXY(305, "Use Proxy"),
|
||||
/**
|
||||
* {@code 307 Temporary Redirect}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.8">HTTP/1.1</a>
|
||||
*/
|
||||
TEMPORARY_REDIRECT(307, "Temporary Redirect"),
|
||||
/**
|
||||
* {@code 308 Resume Incomplete}.
|
||||
* @see <a href="http://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal">A proposal for supporting
|
||||
* resumable POST/PUT HTTP requests in HTTP/1.0</a>
|
||||
*/
|
||||
RESUME_INCOMPLETE(308, "Resume Incomplete"),
|
||||
|
||||
// --- 4xx Client Error ---
|
||||
|
||||
/**
|
||||
* {@code 400 Bad Request}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.1">HTTP/1.1</a>
|
||||
*/
|
||||
BAD_REQUEST(400, "Bad Request"),
|
||||
/**
|
||||
* {@code 401 Unauthorized}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.2">HTTP/1.1</a>
|
||||
*/
|
||||
UNAUTHORIZED(401, "Unauthorized"),
|
||||
/**
|
||||
* {@code 402 Payment Required}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.3">HTTP/1.1</a>
|
||||
*/
|
||||
PAYMENT_REQUIRED(402, "Payment Required"),
|
||||
/**
|
||||
* {@code 403 Forbidden}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.4">HTTP/1.1</a>
|
||||
*/
|
||||
FORBIDDEN(403, "Forbidden"),
|
||||
/**
|
||||
* {@code 404 Not Found}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.5">HTTP/1.1</a>
|
||||
*/
|
||||
NOT_FOUND(404, "Not Found"),
|
||||
/**
|
||||
* {@code 405 Method Not Allowed}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.6">HTTP/1.1</a>
|
||||
*/
|
||||
METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
|
||||
/**
|
||||
* {@code 406 Not Acceptable}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.7">HTTP/1.1</a>
|
||||
*/
|
||||
NOT_ACCEPTABLE(406, "Not Acceptable"),
|
||||
/**
|
||||
* {@code 407 Proxy Authentication Required}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.8">HTTP/1.1</a>
|
||||
*/
|
||||
PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"),
|
||||
/**
|
||||
* {@code 408 Request Timeout}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.9">HTTP/1.1</a>
|
||||
*/
|
||||
REQUEST_TIMEOUT(408, "Request Timeout"),
|
||||
/**
|
||||
* {@code 409 Conflict}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.10">HTTP/1.1</a>
|
||||
*/
|
||||
CONFLICT(409, "Conflict"),
|
||||
/**
|
||||
* {@code 410 Gone}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.11">HTTP/1.1</a>
|
||||
*/
|
||||
GONE(410, "Gone"),
|
||||
/**
|
||||
* {@code 411 Length Required}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.12">HTTP/1.1</a>
|
||||
*/
|
||||
LENGTH_REQUIRED(411, "Length Required"),
|
||||
/**
|
||||
* {@code 412 Precondition failed}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.13">HTTP/1.1</a>
|
||||
*/
|
||||
PRECONDITION_FAILED(412, "Precondition Failed"),
|
||||
/**
|
||||
* {@code 413 Request Entity Too Large}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.14">HTTP/1.1</a>
|
||||
*/
|
||||
REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"),
|
||||
/**
|
||||
* {@code 414 Request-URI Too Long}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.15">HTTP/1.1</a>
|
||||
*/
|
||||
REQUEST_URI_TOO_LONG(414, "Request-URI Too Long"),
|
||||
/**
|
||||
* {@code 415 Unsupported Media Type}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.16">HTTP/1.1</a>
|
||||
*/
|
||||
UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"),
|
||||
/**
|
||||
* {@code 416 Requested Range Not Satisfiable}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.17">HTTP/1.1</a>
|
||||
*/
|
||||
REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested range not satisfiable"),
|
||||
/**
|
||||
* {@code 417 Expectation Failed}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.18">HTTP/1.1</a>
|
||||
*/
|
||||
EXPECTATION_FAILED(417, "Expectation Failed"),
|
||||
/**
|
||||
* {@code 418 I'm a teapot}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2324#section-2.3.2">HTCPCP/1.0</a>
|
||||
*/
|
||||
I_AM_A_TEAPOT(418, "I'm a teapot"),
|
||||
/**
|
||||
* {@code 419 Insufficient Space on Resource}.
|
||||
* @see <a href="http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.4">WebDAV Draft</a>
|
||||
*/
|
||||
INSUFFICIENT_SPACE_ON_RESOURCE(419, "Insufficient Space On Resource"),
|
||||
/**
|
||||
* {@code 420 Method Failure}.
|
||||
* @see <a href="http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.5">WebDAV Draft</a>
|
||||
*/
|
||||
METHOD_FAILURE(420, "Method Failure"),
|
||||
/**
|
||||
* {@code 421 Destination Locked}.
|
||||
* @see <a href="http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.6">WebDAV Draft</a>
|
||||
*/
|
||||
DESTINATION_LOCKED(421, "Destination Locked"),
|
||||
/**
|
||||
* {@code 422 Unprocessable Entity}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc4918#section-11.2">WebDAV</a>
|
||||
*/
|
||||
UNPROCESSABLE_ENTITY(422, "Unprocessable Entity"),
|
||||
/**
|
||||
* {@code 423 Locked}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc4918#section-11.3">WebDAV</a>
|
||||
*/
|
||||
LOCKED(423, "Locked"),
|
||||
/**
|
||||
* {@code 424 Failed Dependency}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc4918#section-11.4">WebDAV</a>
|
||||
*/
|
||||
FAILED_DEPENDENCY(424, "Failed Dependency"),
|
||||
/**
|
||||
* {@code 426 Upgrade Required}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2817#section-6">Upgrading to TLS Within HTTP/1.1</a>
|
||||
*/
|
||||
UPGRADE_REQUIRED(426, "Upgrade Required"),
|
||||
/**
|
||||
* {@code 428 Precondition Required}.
|
||||
* @see <a href="http://tools.ietf.org/html/draft-nottingham-http-new-status-02#section-3">Additional HTTP Status
|
||||
* Codes</a>
|
||||
*/
|
||||
PRECONDITION_REQUIRED(428, "Precondition Required"),
|
||||
/**
|
||||
* {@code 429 Too Many Requests}.
|
||||
* @see <a href="http://tools.ietf.org/html/draft-nottingham-http-new-status-02#section-4">Additional HTTP Status
|
||||
* Codes</a>
|
||||
*/
|
||||
TOO_MANY_REQUESTS(429, "Too Many Requests"),
|
||||
/**
|
||||
* {@code 431 Request Header Fields Too Large}.
|
||||
* @see <a href="http://tools.ietf.org/html/draft-nottingham-http-new-status-02#section-5">Additional HTTP Status
|
||||
* Codes</a>
|
||||
*/
|
||||
REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large"),
|
||||
|
||||
// --- 5xx Server Error ---
|
||||
|
||||
/**
|
||||
* {@code 500 Internal Server Error}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.1">HTTP/1.1</a>
|
||||
*/
|
||||
INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
|
||||
/**
|
||||
* {@code 501 Not Implemented}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.2">HTTP/1.1</a>
|
||||
*/
|
||||
NOT_IMPLEMENTED(501, "Not Implemented"),
|
||||
/**
|
||||
* {@code 502 Bad Gateway}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.3">HTTP/1.1</a>
|
||||
*/
|
||||
BAD_GATEWAY(502, "Bad Gateway"),
|
||||
/**
|
||||
* {@code 503 Service Unavailable}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.4">HTTP/1.1</a>
|
||||
*/
|
||||
SERVICE_UNAVAILABLE(503, "Service Unavailable"),
|
||||
/**
|
||||
* {@code 504 Gateway Timeout}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.5">HTTP/1.1</a>
|
||||
*/
|
||||
GATEWAY_TIMEOUT(504, "Gateway Timeout"),
|
||||
/**
|
||||
* {@code 505 HTTP Version Not Supported}.
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.6">HTTP/1.1</a>
|
||||
*/
|
||||
HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported"),
|
||||
/**
|
||||
* {@code 506 Variant Also Negotiates}
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2295#section-8.1">Transparent Content Negotiation</a>
|
||||
*/
|
||||
VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates"),
|
||||
/**
|
||||
* {@code 507 Insufficient Storage}
|
||||
* @see <a href="http://tools.ietf.org/html/rfc4918#section-11.5">WebDAV</a>
|
||||
*/
|
||||
INSUFFICIENT_STORAGE(507, "Insufficient Storage"),
|
||||
/**
|
||||
* {@code 508 Loop Detected}
|
||||
* @see <a href="http://tools.ietf.org/html/draft-ietf-webdav-bind-27#section-7.2">WebDAV Binding Extensions</a>
|
||||
*/
|
||||
LOOP_DETECTED(508, "Loop Detected"),
|
||||
/**
|
||||
* {@code 509 Bandwidth Limit Exceeded}
|
||||
*/
|
||||
BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded"),
|
||||
/**
|
||||
* {@code 510 Not Extended}
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2774#section-7">HTTP Extension Framework</a>
|
||||
*/
|
||||
NOT_EXTENDED(510, "Not Extended"),
|
||||
/**
|
||||
* {@code 511 Network Authentication Required}.
|
||||
* @see <a href="http://tools.ietf.org/html/draft-nottingham-http-new-status-02#section-6">Additional HTTP Status
|
||||
* Codes</a>
|
||||
*/
|
||||
NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required");
|
||||
|
||||
|
||||
|
||||
private final int value;
|
||||
|
||||
private final String reasonPhrase;
|
||||
|
||||
|
||||
private HttpStatus(int value, String reasonPhrase) {
|
||||
this.value = value;
|
||||
this.reasonPhrase = reasonPhrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the integer value of this status code.
|
||||
*/
|
||||
public int value() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the reason phrase of this status code.
|
||||
*/
|
||||
public String getReasonPhrase() {
|
||||
return reasonPhrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTTP status series of this status code.
|
||||
* @see HttpStatus.Series
|
||||
*/
|
||||
public Series series() {
|
||||
return Series.valueOf(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of this status code.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return Integer.toString(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the enum constant of this type with the specified numeric value.
|
||||
* @param statusCode the numeric value of the enum to be returned
|
||||
* @return the enum constant with the specified numeric value
|
||||
* @throws IllegalArgumentException if this enum has no constant for the specified numeric value
|
||||
*/
|
||||
public static HttpStatus valueOf(int statusCode) {
|
||||
for (HttpStatus status : values()) {
|
||||
if (status.value == statusCode) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No matching constant for [" + statusCode + "]");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Java 5 enumeration of HTTP status series.
|
||||
* <p>Retrievable via {@link HttpStatus#series()}.
|
||||
*/
|
||||
public static enum Series {
|
||||
|
||||
INFORMATIONAL(1),
|
||||
SUCCESSFUL(2),
|
||||
REDIRECTION(3),
|
||||
CLIENT_ERROR(4),
|
||||
SERVER_ERROR(5);
|
||||
|
||||
private final int value;
|
||||
|
||||
private Series(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the integer value of this status series. Ranges from 1 to 5.
|
||||
*/
|
||||
public int value() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
private static Series valueOf(HttpStatus status) {
|
||||
int seriesCode = status.value() / 100;
|
||||
for (Series series : values()) {
|
||||
if (series.value == seriesCode) {
|
||||
return series;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No matching constant for [" + status + "]");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
851
spring-web/src/main/java/org/springframework/http/MediaType.java
Normal file
851
spring-web/src/main/java/org/springframework/http/MediaType.java
Normal file
@@ -0,0 +1,851 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Represents an Internet Media Type, as defined in the HTTP specification.
|
||||
*
|
||||
* <p>Consists of a {@linkplain #getType() type} and a {@linkplain #getSubtype() subtype}.
|
||||
* Also has functionality to parse media types from a string using {@link #parseMediaType(String)},
|
||||
* or multiple comma-separated media types using {@link #parseMediaTypes(String)}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-3.7">HTTP 1.1, section 3.7</a>
|
||||
*/
|
||||
public class MediaType implements Comparable<MediaType> {
|
||||
|
||||
/**
|
||||
* Public constant media type that includes all media ranges (i.e. <code>*/*</code>).
|
||||
*/
|
||||
public static final MediaType ALL;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaType#ALL}.
|
||||
*/
|
||||
public static final String ALL_VALUE = "*/*";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code application/atom+xml}.
|
||||
*/
|
||||
public final static MediaType APPLICATION_ATOM_XML;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaType#APPLICATION_ATOM_XML}.
|
||||
*/
|
||||
public final static String APPLICATION_ATOM_XML_VALUE = "application/atom+xml";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code application/x-www-form-urlencoded}.
|
||||
* */
|
||||
public final static MediaType APPLICATION_FORM_URLENCODED;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaType#APPLICATION_FORM_URLENCODED}.
|
||||
*/
|
||||
public final static String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code application/json}.
|
||||
* */
|
||||
public final static MediaType APPLICATION_JSON;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaType#APPLICATION_JSON}.
|
||||
*/
|
||||
public final static String APPLICATION_JSON_VALUE = "application/json";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code application/octet-stream}.
|
||||
* */
|
||||
public final static MediaType APPLICATION_OCTET_STREAM;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaType#APPLICATION_OCTET_STREAM}.
|
||||
*/
|
||||
public final static String APPLICATION_OCTET_STREAM_VALUE = "application/octet-stream";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code application/xhtml+xml}.
|
||||
* */
|
||||
public final static MediaType APPLICATION_XHTML_XML;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaType#APPLICATION_XHTML_XML}.
|
||||
*/
|
||||
public final static String APPLICATION_XHTML_XML_VALUE = "application/xhtml+xml";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code application/xml}.
|
||||
*/
|
||||
public final static MediaType APPLICATION_XML;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaType#APPLICATION_XML}.
|
||||
*/
|
||||
public final static String APPLICATION_XML_VALUE = "application/xml";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code image/gif}.
|
||||
*/
|
||||
public final static MediaType IMAGE_GIF;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaType#IMAGE_GIF}.
|
||||
*/
|
||||
public final static String IMAGE_GIF_VALUE = "image/gif";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code image/jpeg}.
|
||||
*/
|
||||
public final static MediaType IMAGE_JPEG;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaType#IMAGE_JPEG}.
|
||||
*/
|
||||
public final static String IMAGE_JPEG_VALUE = "image/jpeg";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code image/png}.
|
||||
*/
|
||||
public final static MediaType IMAGE_PNG;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaType#IMAGE_PNG}.
|
||||
*/
|
||||
public final static String IMAGE_PNG_VALUE = "image/png";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code multipart/form-data}.
|
||||
* */
|
||||
public final static MediaType MULTIPART_FORM_DATA;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaType#MULTIPART_FORM_DATA}.
|
||||
*/
|
||||
public final static String MULTIPART_FORM_DATA_VALUE = "multipart/form-data";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code text/html}.
|
||||
* */
|
||||
public final static MediaType TEXT_HTML;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaType#TEXT_HTML}.
|
||||
*/
|
||||
public final static String TEXT_HTML_VALUE = "text/html";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code text/plain}.
|
||||
* */
|
||||
public final static MediaType TEXT_PLAIN;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaType#TEXT_PLAIN}.
|
||||
*/
|
||||
public final static String TEXT_PLAIN_VALUE = "text/plain";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code text/xml}.
|
||||
* */
|
||||
public final static MediaType TEXT_XML;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaType#TEXT_XML}.
|
||||
*/
|
||||
public final static String TEXT_XML_VALUE = "text/xml";
|
||||
|
||||
|
||||
private static final BitSet TOKEN;
|
||||
|
||||
private static final String WILDCARD_TYPE = "*";
|
||||
|
||||
private static final String PARAM_QUALITY_FACTOR = "q";
|
||||
|
||||
private static final String PARAM_CHARSET = "charset";
|
||||
|
||||
|
||||
private final String type;
|
||||
|
||||
private final String subtype;
|
||||
|
||||
private final Map<String, String> parameters;
|
||||
|
||||
|
||||
static {
|
||||
// variable names refer to RFC 2616, section 2.2
|
||||
BitSet ctl = new BitSet(128);
|
||||
for (int i=0; i <= 31; i++) {
|
||||
ctl.set(i);
|
||||
}
|
||||
ctl.set(127);
|
||||
|
||||
BitSet separators = new BitSet(128);
|
||||
separators.set('(');
|
||||
separators.set(')');
|
||||
separators.set('<');
|
||||
separators.set('>');
|
||||
separators.set('@');
|
||||
separators.set(',');
|
||||
separators.set(';');
|
||||
separators.set(':');
|
||||
separators.set('\\');
|
||||
separators.set('\"');
|
||||
separators.set('/');
|
||||
separators.set('[');
|
||||
separators.set(']');
|
||||
separators.set('?');
|
||||
separators.set('=');
|
||||
separators.set('{');
|
||||
separators.set('}');
|
||||
separators.set(' ');
|
||||
separators.set('\t');
|
||||
|
||||
TOKEN = new BitSet(128);
|
||||
TOKEN.set(0, 128);
|
||||
TOKEN.andNot(ctl);
|
||||
TOKEN.andNot(separators);
|
||||
|
||||
ALL = MediaType.valueOf(ALL_VALUE);
|
||||
APPLICATION_ATOM_XML = MediaType.valueOf(APPLICATION_ATOM_XML_VALUE);
|
||||
APPLICATION_FORM_URLENCODED = MediaType.valueOf(APPLICATION_FORM_URLENCODED_VALUE);
|
||||
APPLICATION_JSON = MediaType.valueOf(APPLICATION_JSON_VALUE);
|
||||
APPLICATION_OCTET_STREAM = MediaType.valueOf(APPLICATION_OCTET_STREAM_VALUE);
|
||||
APPLICATION_XHTML_XML = MediaType.valueOf(APPLICATION_XHTML_XML_VALUE);
|
||||
APPLICATION_XML = MediaType.valueOf(APPLICATION_XML_VALUE);
|
||||
IMAGE_GIF = MediaType.valueOf(IMAGE_GIF_VALUE);
|
||||
IMAGE_JPEG = MediaType.valueOf(IMAGE_JPEG_VALUE);
|
||||
IMAGE_PNG = MediaType.valueOf(IMAGE_PNG_VALUE);
|
||||
MULTIPART_FORM_DATA = MediaType.valueOf(MULTIPART_FORM_DATA_VALUE);
|
||||
TEXT_HTML = MediaType.valueOf(TEXT_HTML_VALUE);
|
||||
TEXT_PLAIN = MediaType.valueOf(TEXT_PLAIN_VALUE);
|
||||
TEXT_XML = MediaType.valueOf(TEXT_XML_VALUE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code MediaType} for the given primary type.
|
||||
* <p>The {@linkplain #getSubtype() subtype} is set to <code>*</code>, parameters empty.
|
||||
* @param type the primary type
|
||||
* @throws IllegalArgumentException if any of the parameters contain illegal characters
|
||||
*/
|
||||
public MediaType(String type) {
|
||||
this(type, WILDCARD_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code MediaType} for the given primary type and subtype.
|
||||
* <p>The parameters are empty.
|
||||
* @param type the primary type
|
||||
* @param subtype the subtype
|
||||
* @throws IllegalArgumentException if any of the parameters contain illegal characters
|
||||
*/
|
||||
public MediaType(String type, String subtype) {
|
||||
this(type, subtype, Collections.<String, String>emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code MediaType} for the given type, subtype, and character set.
|
||||
* @param type the primary type
|
||||
* @param subtype the subtype
|
||||
* @param charSet the character set
|
||||
* @throws IllegalArgumentException if any of the parameters contain illegal characters
|
||||
*/
|
||||
public MediaType(String type, String subtype, Charset charSet) {
|
||||
this(type, subtype, Collections.singletonMap(PARAM_CHARSET, charSet.name()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code MediaType} for the given type, subtype, and quality value.
|
||||
*
|
||||
* @param type the primary type
|
||||
* @param subtype the subtype
|
||||
* @param qualityValue the quality value
|
||||
* @throws IllegalArgumentException if any of the parameters contain illegal characters
|
||||
*/
|
||||
public MediaType(String type, String subtype, double qualityValue) {
|
||||
this(type, subtype, Collections.singletonMap(PARAM_QUALITY_FACTOR, Double.toString(qualityValue)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy-constructor that copies the type and subtype of the given {@code MediaType},
|
||||
* and allows for different parameter.
|
||||
* @param other the other media type
|
||||
* @param parameters the parameters, may be <code>null</code>
|
||||
* @throws IllegalArgumentException if any of the parameters contain illegal characters
|
||||
*/
|
||||
public MediaType(MediaType other, Map<String, String> parameters) {
|
||||
this(other.getType(), other.getSubtype(), parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code MediaType} for the given type, subtype, and parameters.
|
||||
* @param type the primary type
|
||||
* @param subtype the subtype
|
||||
* @param parameters the parameters, may be <code>null</code>
|
||||
* @throws IllegalArgumentException if any of the parameters contain illegal characters
|
||||
*/
|
||||
public MediaType(String type, String subtype, Map<String, String> parameters) {
|
||||
Assert.hasLength(type, "'type' must not be empty");
|
||||
Assert.hasLength(subtype, "'subtype' must not be empty");
|
||||
checkToken(type);
|
||||
checkToken(subtype);
|
||||
this.type = type.toLowerCase(Locale.ENGLISH);
|
||||
this.subtype = subtype.toLowerCase(Locale.ENGLISH);
|
||||
if (!CollectionUtils.isEmpty(parameters)) {
|
||||
Map<String, String> m = new LinkedCaseInsensitiveMap<String>(parameters.size(), Locale.ENGLISH);
|
||||
for (Map.Entry<String, String> entry : parameters.entrySet()) {
|
||||
String attribute = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
checkParameters(attribute, value);
|
||||
m.put(attribute, value);
|
||||
}
|
||||
this.parameters = Collections.unmodifiableMap(m);
|
||||
}
|
||||
else {
|
||||
this.parameters = Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given token string for illegal characters, as defined in RFC 2616, section 2.2.
|
||||
* @throws IllegalArgumentException in case of illegal characters
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-2.2">HTTP 1.1, section 2.2</a>
|
||||
*/
|
||||
private void checkToken(String s) {
|
||||
for (int i=0; i < s.length(); i++ ) {
|
||||
char ch = s.charAt(i);
|
||||
if (!TOKEN.get(ch)) {
|
||||
throw new IllegalArgumentException("Invalid token character '" + ch + "' in token \"" + s + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkParameters(String attribute, String value) {
|
||||
Assert.hasLength(attribute, "parameter attribute must not be empty");
|
||||
Assert.hasLength(value, "parameter value must not be empty");
|
||||
checkToken(attribute);
|
||||
if (PARAM_QUALITY_FACTOR.equals(attribute)) {
|
||||
value = unquote(value);
|
||||
double d = Double.parseDouble(value);
|
||||
Assert.isTrue(d >= 0D && d <= 1D,
|
||||
"Invalid quality value \"" + value + "\": should be between 0.0 and 1.0");
|
||||
}
|
||||
else if (PARAM_CHARSET.equals(attribute)) {
|
||||
value = unquote(value);
|
||||
Charset.forName(value);
|
||||
}
|
||||
else if (!isQuotedString(value)) {
|
||||
checkToken(value);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isQuotedString(String s) {
|
||||
return s.length() > 1 && s.startsWith("\"") && s.endsWith("\"") ;
|
||||
}
|
||||
|
||||
private String unquote(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
return isQuotedString(s) ? s.substring(1, s.length() - 1) : s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the primary type.
|
||||
*/
|
||||
public String getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the {@linkplain #getType() type} is the wildcard character <code>*</code> or not.
|
||||
*/
|
||||
public boolean isWildcardType() {
|
||||
return WILDCARD_TYPE.equals(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the subtype.
|
||||
*/
|
||||
public String getSubtype() {
|
||||
return this.subtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the {@linkplain #getSubtype() subtype} is the wildcard character <code>*</code> or not.
|
||||
* @return whether the subtype is <code>*</code>
|
||||
*/
|
||||
public boolean isWildcardSubtype() {
|
||||
return WILDCARD_TYPE.equals(subtype);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this media type is concrete, i.e. whether neither the type or subtype is a wildcard
|
||||
* character <code>*</code>.
|
||||
* @return whether this media type is concrete
|
||||
*/
|
||||
public boolean isConcrete() {
|
||||
return !isWildcardType() && !isWildcardSubtype();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the character set, as indicated by a <code>charset</code> parameter, if any.
|
||||
* @return the character set; or <code>null</code> if not available
|
||||
*/
|
||||
public Charset getCharSet() {
|
||||
String charSet = getParameter(PARAM_CHARSET);
|
||||
return (charSet != null ? Charset.forName(unquote(charSet)) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the quality value, as indicated by a <code>q</code> parameter, if any.
|
||||
* Defaults to <code>1.0</code>.
|
||||
* @return the quality factory
|
||||
*/
|
||||
public double getQualityValue() {
|
||||
String qualityFactory = getParameter(PARAM_QUALITY_FACTOR);
|
||||
return (qualityFactory != null ? Double.parseDouble(unquote(qualityFactory)) : 1D);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a generic parameter value, given a parameter name.
|
||||
* @param name the parameter name
|
||||
* @return the parameter value; or <code>null</code> if not present
|
||||
*/
|
||||
public String getParameter(String name) {
|
||||
return this.parameters.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether this {@code MediaType} includes the given media type.
|
||||
* <p>For instance, {@code text/*} includes {@code text/plain} and {@code text/html}, and {@code application/*+xml}
|
||||
* includes {@code application/soap+xml}, etc. This method is <b>not</b> symmetric.
|
||||
* @param other the reference media type with which to compare
|
||||
* @return <code>true</code> if this media type includes the given media type; <code>false</code> otherwise
|
||||
*/
|
||||
public boolean includes(MediaType other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (this.isWildcardType()) {
|
||||
// */* includes anything
|
||||
return true;
|
||||
}
|
||||
else if (this.type.equals(other.type)) {
|
||||
if (this.subtype.equals(other.subtype) || this.isWildcardSubtype()) {
|
||||
return true;
|
||||
}
|
||||
// application/*+xml includes application/soap+xml
|
||||
int thisPlusIdx = this.subtype.indexOf('+');
|
||||
int otherPlusIdx = other.subtype.indexOf('+');
|
||||
if (thisPlusIdx != -1 && otherPlusIdx != -1) {
|
||||
String thisSubtypeNoSuffix = this.subtype.substring(0, thisPlusIdx);
|
||||
String thisSubtypeSuffix = this.subtype.substring(thisPlusIdx + 1);
|
||||
String otherSubtypeSuffix = other.subtype.substring(otherPlusIdx + 1);
|
||||
if (thisSubtypeSuffix.equals(otherSubtypeSuffix) && WILDCARD_TYPE.equals(thisSubtypeNoSuffix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether this {@code MediaType} is compatible with the given media type.
|
||||
* <p>For instance, {@code text/*} is compatible with {@code text/plain}, {@code text/html}, and vice versa.
|
||||
* In effect, this method is similar to {@link #includes(MediaType)}, except that it <b>is</b> symmetric.
|
||||
* @param other the reference media type with which to compare
|
||||
* @return <code>true</code> if this media type is compatible with the given media type; <code>false</code> otherwise
|
||||
*/
|
||||
public boolean isCompatibleWith(MediaType other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (isWildcardType() || other.isWildcardType()) {
|
||||
return true;
|
||||
}
|
||||
else if (this.type.equals(other.type)) {
|
||||
if (this.subtype.equals(other.subtype) || this.isWildcardSubtype() || other.isWildcardSubtype()) {
|
||||
return true;
|
||||
}
|
||||
// application/*+xml is compatible with application/soap+xml, and vice-versa
|
||||
int thisPlusIdx = this.subtype.indexOf('+');
|
||||
int otherPlusIdx = other.subtype.indexOf('+');
|
||||
if (thisPlusIdx != -1 && otherPlusIdx != -1) {
|
||||
String thisSubtypeNoSuffix = this.subtype.substring(0, thisPlusIdx);
|
||||
String otherSubtypeNoSuffix = other.subtype.substring(0, otherPlusIdx);
|
||||
|
||||
String thisSubtypeSuffix = this.subtype.substring(thisPlusIdx + 1);
|
||||
String otherSubtypeSuffix = other.subtype.substring(otherPlusIdx + 1);
|
||||
|
||||
if (thisSubtypeSuffix.equals(otherSubtypeSuffix) &&
|
||||
(WILDCARD_TYPE.equals(thisSubtypeNoSuffix) || WILDCARD_TYPE.equals(otherSubtypeNoSuffix))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this {@code MediaType} to another alphabetically.
|
||||
* @param other media type to compare to
|
||||
* @see #sortBySpecificity(List)
|
||||
*/
|
||||
public int compareTo(MediaType other) {
|
||||
int comp = this.type.compareToIgnoreCase(other.type);
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
comp = this.subtype.compareToIgnoreCase(other.subtype);
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
comp = this.parameters.size() - other.parameters.size();
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
TreeSet<String> thisAttributes = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
|
||||
thisAttributes.addAll(this.parameters.keySet());
|
||||
TreeSet<String> otherAttributes = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
|
||||
otherAttributes.addAll(other.parameters.keySet());
|
||||
Iterator<String> thisAttributesIterator = thisAttributes.iterator();
|
||||
Iterator<String> otherAttributesIterator = otherAttributes.iterator();
|
||||
while (thisAttributesIterator.hasNext()) {
|
||||
String thisAttribute = thisAttributesIterator.next();
|
||||
String otherAttribute = otherAttributesIterator.next();
|
||||
comp = thisAttribute.compareToIgnoreCase(otherAttribute);
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
String thisValue = this.parameters.get(thisAttribute);
|
||||
String otherValue = other.parameters.get(otherAttribute);
|
||||
if (otherValue == null) {
|
||||
otherValue = "";
|
||||
}
|
||||
comp = thisValue.compareTo(otherValue);
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof MediaType)) {
|
||||
return false;
|
||||
}
|
||||
MediaType otherType = (MediaType) other;
|
||||
return (this.type.equalsIgnoreCase(otherType.type) && this.subtype.equalsIgnoreCase(otherType.subtype) &&
|
||||
this.parameters.equals(otherType.parameters));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.type.hashCode();
|
||||
result = 31 * result + this.subtype.hashCode();
|
||||
result = 31 * result + this.parameters.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
appendTo(builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void appendTo(StringBuilder builder) {
|
||||
builder.append(this.type);
|
||||
builder.append('/');
|
||||
builder.append(this.subtype);
|
||||
appendTo(this.parameters, builder);
|
||||
}
|
||||
|
||||
private void appendTo(Map<String, String> map, StringBuilder builder) {
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
builder.append(';');
|
||||
builder.append(entry.getKey());
|
||||
builder.append('=');
|
||||
builder.append(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the given String value into a {@code MediaType} object,
|
||||
* with this method name following the 'valueOf' naming convention
|
||||
* (as supported by {@link org.springframework.core.convert.ConversionService}.
|
||||
* @see #parseMediaType(String)
|
||||
*/
|
||||
public static MediaType valueOf(String value) {
|
||||
return parseMediaType(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given String into a single {@code MediaType}.
|
||||
* @param mediaType the string to parse
|
||||
* @return the media type
|
||||
* @throws IllegalArgumentException if the string cannot be parsed
|
||||
*/
|
||||
public static MediaType parseMediaType(String mediaType) {
|
||||
Assert.hasLength(mediaType, "'mediaType' must not be empty");
|
||||
String[] parts = StringUtils.tokenizeToStringArray(mediaType, ";");
|
||||
|
||||
String fullType = parts[0].trim();
|
||||
// java.net.HttpURLConnection returns a *; q=.2 Accept header
|
||||
if (WILDCARD_TYPE.equals(fullType)) {
|
||||
fullType = "*/*";
|
||||
}
|
||||
int subIndex = fullType.indexOf('/');
|
||||
if (subIndex == -1) {
|
||||
throw new IllegalArgumentException("\"" + mediaType + "\" does not contain '/'");
|
||||
}
|
||||
if (subIndex == fullType.length() - 1) {
|
||||
throw new IllegalArgumentException("\"" + mediaType + "\" does not contain subtype after '/'");
|
||||
}
|
||||
String type = fullType.substring(0, subIndex);
|
||||
String subtype = fullType.substring(subIndex + 1, fullType.length());
|
||||
if (WILDCARD_TYPE.equals(type) && !WILDCARD_TYPE.equals(subtype)) {
|
||||
throw new IllegalArgumentException("A wildcard type is legal only in '*/*' (all media types).");
|
||||
}
|
||||
|
||||
Map<String, String> parameters = null;
|
||||
if (parts.length > 1) {
|
||||
parameters = new LinkedHashMap<String, String>(parts.length - 1);
|
||||
for (int i = 1; i < parts.length; i++) {
|
||||
String parameter = parts[i];
|
||||
int eqIndex = parameter.indexOf('=');
|
||||
if (eqIndex != -1) {
|
||||
String attribute = parameter.substring(0, eqIndex);
|
||||
String value = parameter.substring(eqIndex + 1, parameter.length());
|
||||
parameters.put(attribute, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new MediaType(type, subtype, parameters);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the given, comma-separated string into a list of {@code MediaType} objects.
|
||||
* <p>This method can be used to parse an Accept or Content-Type header.
|
||||
* @param mediaTypes the string to parse
|
||||
* @return the list of media types
|
||||
* @throws IllegalArgumentException if the string cannot be parsed
|
||||
*/
|
||||
public static List<MediaType> parseMediaTypes(String mediaTypes) {
|
||||
if (!StringUtils.hasLength(mediaTypes)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String[] tokens = mediaTypes.split(",\\s*");
|
||||
List<MediaType> result = new ArrayList<MediaType>(tokens.length);
|
||||
for (String token : tokens) {
|
||||
result.add(parseMediaType(token));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of the given list of {@code MediaType} objects.
|
||||
* <p>This method can be used to for an {@code Accept} or {@code Content-Type} header.
|
||||
* @param mediaTypes the string to parse
|
||||
* @return the list of media types
|
||||
* @throws IllegalArgumentException if the String cannot be parsed
|
||||
*/
|
||||
public static String toString(Collection<MediaType> mediaTypes) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Iterator<MediaType> iterator = mediaTypes.iterator(); iterator.hasNext();) {
|
||||
MediaType mediaType = iterator.next();
|
||||
mediaType.appendTo(builder);
|
||||
if (iterator.hasNext()) {
|
||||
builder.append(", ");
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the given list of {@code MediaType} objects by specificity.
|
||||
* <p>Given two media types:
|
||||
* <ol>
|
||||
* <li>if either media type has a {@linkplain #isWildcardType() wildcard type}, then the media type without the
|
||||
* wildcard is ordered before the other.</li>
|
||||
* <li>if the two media types have different {@linkplain #getType() types}, then they are considered equal and
|
||||
* remain their current order.</li>
|
||||
* <li>if either media type has a {@linkplain #isWildcardSubtype() wildcard subtype}, then the media type without
|
||||
* the wildcard is sorted before the other.</li>
|
||||
* <li>if the two media types have different {@linkplain #getSubtype() subtypes}, then they are considered equal
|
||||
* and remain their current order.</li>
|
||||
* <li>if the two media types have different {@linkplain #getQualityValue() quality value}, then the media type
|
||||
* with the highest quality value is ordered before the other.</li>
|
||||
* <li>if the two media types have a different amount of {@linkplain #getParameter(String) parameters}, then the
|
||||
* media type with the most parameters is ordered before the other.</li>
|
||||
* </ol>
|
||||
* <p>For example:
|
||||
* <blockquote>audio/basic < audio/* < */*</blockquote>
|
||||
* <blockquote>audio/* < audio/*;q=0.7; audio/*;q=0.3</blockquote>
|
||||
* <blockquote>audio/basic;level=1 < audio/basic</blockquote>
|
||||
* <blockquote>audio/basic == text/html</blockquote>
|
||||
* <blockquote>audio/basic == audio/wave</blockquote>
|
||||
* @param mediaTypes the list of media types to be sorted
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2616#section-14.1">HTTP 1.1, section 14.1</a>
|
||||
*/
|
||||
public static void sortBySpecificity(List<MediaType> mediaTypes) {
|
||||
Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
|
||||
if (mediaTypes.size() > 1) {
|
||||
Collections.sort(mediaTypes, SPECIFICITY_COMPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the given list of {@code MediaType} objects by quality value.
|
||||
* <p>Given two media types:
|
||||
* <ol>
|
||||
* <li>if the two media types have different {@linkplain #getQualityValue() quality value}, then the media type
|
||||
* with the highest quality value is ordered before the other.</li>
|
||||
* <li>if either media type has a {@linkplain #isWildcardType() wildcard type}, then the media type without the
|
||||
* wildcard is ordered before the other.</li>
|
||||
* <li>if the two media types have different {@linkplain #getType() types}, then they are considered equal and
|
||||
* remain their current order.</li>
|
||||
* <li>if either media type has a {@linkplain #isWildcardSubtype() wildcard subtype}, then the media type without
|
||||
* the wildcard is sorted before the other.</li>
|
||||
* <li>if the two media types have different {@linkplain #getSubtype() subtypes}, then they are considered equal
|
||||
* and remain their current order.</li>
|
||||
* <li>if the two media types have a different amount of {@linkplain #getParameter(String) parameters}, then the
|
||||
* media type with the most parameters is ordered before the other.</li>
|
||||
* </ol>
|
||||
* @param mediaTypes the list of media types to be sorted
|
||||
* @see #getQualityValue()
|
||||
*/
|
||||
public static void sortByQualityValue(List<MediaType> mediaTypes) {
|
||||
Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
|
||||
if (mediaTypes.size() > 1) {
|
||||
Collections.sort(mediaTypes, QUALITY_VALUE_COMPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Comparator used by {@link #sortBySpecificity(List)}.
|
||||
*/
|
||||
public static final Comparator<MediaType> SPECIFICITY_COMPARATOR = new Comparator<MediaType>() {
|
||||
|
||||
public int compare(MediaType mediaType1, MediaType mediaType2) {
|
||||
if (mediaType1.isWildcardType() && !mediaType2.isWildcardType()) { // */* < audio/*
|
||||
return 1;
|
||||
}
|
||||
else if (mediaType2.isWildcardType() && !mediaType1.isWildcardType()) { // audio/* > */*
|
||||
return -1;
|
||||
}
|
||||
else if (!mediaType1.getType().equals(mediaType2.getType())) { // audio/basic == text/html
|
||||
return 0;
|
||||
}
|
||||
else { // mediaType1.getType().equals(mediaType2.getType())
|
||||
if (mediaType1.isWildcardSubtype() && !mediaType2.isWildcardSubtype()) { // audio/* < audio/basic
|
||||
return 1;
|
||||
}
|
||||
else if (mediaType2.isWildcardSubtype() && !mediaType1.isWildcardSubtype()) { // audio/basic > audio/*
|
||||
return -1;
|
||||
}
|
||||
else if (!mediaType1.getSubtype().equals(mediaType2.getSubtype())) { // audio/basic == audio/wave
|
||||
return 0;
|
||||
}
|
||||
else { // mediaType2.getSubtype().equals(mediaType2.getSubtype())
|
||||
double quality1 = mediaType1.getQualityValue();
|
||||
double quality2 = mediaType2.getQualityValue();
|
||||
int qualityComparison = Double.compare(quality2, quality1);
|
||||
if (qualityComparison != 0) {
|
||||
return qualityComparison; // audio/*;q=0.7 < audio/*;q=0.3
|
||||
}
|
||||
else {
|
||||
int paramsSize1 = mediaType1.parameters.size();
|
||||
int paramsSize2 = mediaType2.parameters.size();
|
||||
return (paramsSize2 < paramsSize1 ? -1 : (paramsSize2 == paramsSize1 ? 0 : 1)); // audio/basic;level=1 < audio/basic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Comparator used by {@link #sortByQualityValue(List)}.
|
||||
*/
|
||||
public static final Comparator<MediaType> QUALITY_VALUE_COMPARATOR = new Comparator<MediaType>() {
|
||||
|
||||
public int compare(MediaType mediaType1, MediaType mediaType2) {
|
||||
double quality1 = mediaType1.getQualityValue();
|
||||
double quality2 = mediaType2.getQualityValue();
|
||||
int qualityComparison = Double.compare(quality2, quality1);
|
||||
if (qualityComparison != 0) {
|
||||
return qualityComparison; // audio/*;q=0.7 < audio/*;q=0.3
|
||||
}
|
||||
else if (mediaType1.isWildcardType() && !mediaType2.isWildcardType()) { // */* < audio/*
|
||||
return 1;
|
||||
}
|
||||
else if (mediaType2.isWildcardType() && !mediaType1.isWildcardType()) { // audio/* > */*
|
||||
return -1;
|
||||
}
|
||||
else if (!mediaType1.getType().equals(mediaType2.getType())) { // audio/basic == text/html
|
||||
return 0;
|
||||
}
|
||||
else { // mediaType1.getType().equals(mediaType2.getType())
|
||||
if (mediaType1.isWildcardSubtype() && !mediaType2.isWildcardSubtype()) { // audio/* < audio/basic
|
||||
return 1;
|
||||
}
|
||||
else if (mediaType2.isWildcardSubtype() && !mediaType1.isWildcardSubtype()) { // audio/basic > audio/*
|
||||
return -1;
|
||||
}
|
||||
else if (!mediaType1.getSubtype().equals(mediaType2.getSubtype())) { // audio/basic == audio/wave
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
int paramsSize1 = mediaType1.parameters.size();
|
||||
int paramsSize2 = mediaType2.parameters.size();
|
||||
return (paramsSize2 < paramsSize1 ? -1 : (paramsSize2 == paramsSize1 ? 0 : 1)); // audio/basic;level=1 < audio/basic
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http;
|
||||
|
||||
import java.beans.PropertyEditorSupport;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link java.beans.PropertyEditor Editor} for {@link MediaType}
|
||||
* descriptors, to automatically convert <code>String</code> specifications
|
||||
* (e.g. <code>"text/html"</code>) to <code>MediaType</code> properties.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see MediaType
|
||||
*/
|
||||
public class MediaTypeEditor extends PropertyEditorSupport {
|
||||
|
||||
@Override
|
||||
public void setAsText(String text) {
|
||||
if (StringUtils.hasText(text)) {
|
||||
setValue(MediaType.parseMediaType(text));
|
||||
}
|
||||
else {
|
||||
setValue(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAsText() {
|
||||
MediaType mediaType = (MediaType) getValue();
|
||||
return (mediaType != null ? mediaType.toString() : "");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http;
|
||||
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Extension of {@link HttpEntity} that adds a {@link HttpStatus} status code.
|
||||
*
|
||||
* <p>Returned by {@link org.springframework.web.client.RestTemplate#getForEntity}:
|
||||
* <pre class="code">
|
||||
* ResponseEntity<String> entity = template.getForEntity("http://example.com", String.class);
|
||||
* String body = entity.getBody();
|
||||
* MediaType contentType = entity.getHeaders().getContentType();
|
||||
* HttpStatus statusCode = entity.getStatusCode();
|
||||
* </pre>
|
||||
* <p>Can also be used in Spring MVC, as a return value from a @Controller method:
|
||||
* <pre class="code">
|
||||
* @RequestMapping("/handle")
|
||||
* public ResponseEntity<String> handle() {
|
||||
* HttpHeaders responseHeaders = new HttpHeaders();
|
||||
* responseHeaders.set("MyResponseHeader", "MyValue");
|
||||
* return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0.2
|
||||
* @see #getStatusCode()
|
||||
*/
|
||||
public class ResponseEntity<T> extends HttpEntity<T> {
|
||||
|
||||
private final HttpStatus statusCode;
|
||||
|
||||
/**
|
||||
* Create a new {@code ResponseEntity} with the given status code, and no body nor headers.
|
||||
* @param statusCode the status code
|
||||
*/
|
||||
public ResponseEntity(HttpStatus statusCode) {
|
||||
super();
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code ResponseEntity} with the given body and status code, and no headers.
|
||||
* @param body the entity body
|
||||
* @param statusCode the status code
|
||||
*/
|
||||
public ResponseEntity(T body, HttpStatus statusCode) {
|
||||
super(body);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code HttpEntity} with the given headers and status code, and no body.
|
||||
* @param headers the entity headers
|
||||
* @param statusCode the status code
|
||||
*/
|
||||
public ResponseEntity(MultiValueMap<String, String> headers, HttpStatus statusCode) {
|
||||
super(headers);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code HttpEntity} with the given body, headers, and status code.
|
||||
* @param body the entity body
|
||||
* @param headers the entity headers
|
||||
* @param statusCode the status code
|
||||
*/
|
||||
public ResponseEntity(T body, MultiValueMap<String, String> headers, HttpStatus statusCode) {
|
||||
super(body, headers);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HTTP status code of the response.
|
||||
* @return the HTTP status as an HttpStatus enum value
|
||||
*/
|
||||
public HttpStatus getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder("<");
|
||||
builder.append(statusCode.toString());
|
||||
builder.append(' ');
|
||||
builder.append(statusCode.getReasonPhrase());
|
||||
builder.append(',');
|
||||
T body = getBody();
|
||||
HttpHeaders headers = getHeaders();
|
||||
if (body != null) {
|
||||
builder.append(body);
|
||||
if (headers != null) {
|
||||
builder.append(',');
|
||||
}
|
||||
}
|
||||
if (headers != null) {
|
||||
builder.append(headers);
|
||||
}
|
||||
builder.append('>');
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
/**
|
||||
* Abstract base for {@link ClientHttpRequest} that buffers output in a byte array before sending it over the wire.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0.6
|
||||
*/
|
||||
abstract class AbstractBufferingClientHttpRequest extends AbstractClientHttpRequest {
|
||||
|
||||
private ByteArrayOutputStream bufferedOutput = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException {
|
||||
return this.bufferedOutput;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
|
||||
byte[] bytes = this.bufferedOutput.toByteArray();
|
||||
if (headers.getContentLength() == -1) {
|
||||
headers.setContentLength(bytes.length);
|
||||
}
|
||||
ClientHttpResponse result = executeInternal(headers, bytes);
|
||||
this.bufferedOutput = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract template method that writes the given headers and content to the HTTP request.
|
||||
* @param headers the HTTP headers
|
||||
* @param bufferedOutput the body content
|
||||
* @return the response object for the executed request
|
||||
*/
|
||||
protected abstract ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput)
|
||||
throws IOException;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract base for {@link ClientHttpRequest} that makes sure that headers and body are not written multiple times.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
|
||||
|
||||
private final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
private boolean executed = false;
|
||||
|
||||
|
||||
public final HttpHeaders getHeaders() {
|
||||
return (this.executed ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
|
||||
}
|
||||
|
||||
public final OutputStream getBody() throws IOException {
|
||||
checkExecuted();
|
||||
return getBodyInternal(this.headers);
|
||||
}
|
||||
|
||||
public final ClientHttpResponse execute() throws IOException {
|
||||
checkExecuted();
|
||||
ClientHttpResponse result = executeInternal(this.headers);
|
||||
this.executed = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
private void checkExecuted() {
|
||||
Assert.state(!this.executed, "ClientHttpRequest already executed");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Abstract template method that returns the body.
|
||||
* @param headers the HTTP headers
|
||||
* @return the body output stream
|
||||
*/
|
||||
protected abstract OutputStream getBodyInternal(HttpHeaders headers) throws IOException;
|
||||
|
||||
/**
|
||||
* Abstract template method that writes the given headers and content to the HTTP request.
|
||||
* @param headers the HTTP headers
|
||||
* @return the response object for the executed request
|
||||
*/
|
||||
protected abstract ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link ClientHttpRequestFactory} implementations that decorate another request factory.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
*/
|
||||
public abstract class AbstractClientHttpRequestFactoryWrapper implements ClientHttpRequestFactory {
|
||||
|
||||
private final ClientHttpRequestFactory requestFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@code AbstractClientHttpRequestFactoryWrapper} wrapping the given request factory.
|
||||
* @param requestFactory the request factory to be wrapped
|
||||
*/
|
||||
protected AbstractClientHttpRequestFactoryWrapper(ClientHttpRequestFactory requestFactory) {
|
||||
Assert.notNull(requestFactory, "'requestFactory' must not be null");
|
||||
this.requestFactory = requestFactory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This implementation simply calls {@link #createRequest(URI, HttpMethod, ClientHttpRequestFactory)}
|
||||
* with the wrapped request factory provided to the
|
||||
* {@linkplain #AbstractClientHttpRequestFactoryWrapper(ClientHttpRequestFactory) constructor}.
|
||||
*/
|
||||
public final ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
|
||||
return createRequest(uri, httpMethod, requestFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ClientHttpRequest} for the specified URI and HTTP method by using the
|
||||
* passed-on request factory.
|
||||
* <p>Called from {@link #createRequest(URI, HttpMethod)}.
|
||||
* @param uri the URI to create a request for
|
||||
* @param httpMethod the HTTP method to execute
|
||||
* @param requestFactory the wrapped request factory
|
||||
* @return the created request
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
protected abstract ClientHttpRequest createRequest(
|
||||
URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* Abstract base for {@link ClientHttpResponse}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public abstract class AbstractClientHttpResponse implements ClientHttpResponse {
|
||||
|
||||
public HttpStatus getStatusCode() throws IOException {
|
||||
return HttpStatus.valueOf(getRawStatusCode());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* Wrapper for a {@link ClientHttpRequestFactory} that buffers all outgoing and incoming streams in memory.
|
||||
*
|
||||
* <p>Using this wrapper allows for multiple reads of the {@linkplain ClientHttpResponse#getBody() response body}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
*/
|
||||
public class BufferingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {
|
||||
|
||||
public BufferingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory) {
|
||||
super(requestFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory)
|
||||
throws IOException {
|
||||
ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod);
|
||||
if (shouldBuffer(uri, httpMethod)) {
|
||||
return new BufferingClientHttpRequestWrapper(request);
|
||||
}
|
||||
else {
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the request/response exchange for the given URI and method should be buffered in memory.
|
||||
*
|
||||
* <p>Default implementation returns {@code true} for all URIs and methods. Subclasses can override this method to
|
||||
* change this behavior.
|
||||
*
|
||||
* @param uri the URI
|
||||
* @param httpMethod the method
|
||||
* @return {@code true} if the exchange should be buffered; {@code false} otherwise
|
||||
*/
|
||||
protected boolean shouldBuffer(URI uri, HttpMethod httpMethod) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* Simple implementation of {@link ClientHttpRequest} that wraps another request.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
*/
|
||||
final class BufferingClientHttpRequestWrapper extends AbstractBufferingClientHttpRequest {
|
||||
|
||||
private final ClientHttpRequest request;
|
||||
|
||||
|
||||
BufferingClientHttpRequestWrapper(ClientHttpRequest request) {
|
||||
Assert.notNull(request, "'request' must not be null");
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
|
||||
public HttpMethod getMethod() {
|
||||
return this.request.getMethod();
|
||||
}
|
||||
|
||||
public URI getURI() {
|
||||
return this.request.getURI();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
|
||||
this.request.getHeaders().putAll(headers);
|
||||
OutputStream body = this.request.getBody();
|
||||
FileCopyUtils.copy(bufferedOutput, body);
|
||||
ClientHttpResponse response = this.request.execute();
|
||||
return new BufferingClientHttpResponseWrapper(response);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* Simple implementation of {@link ClientHttpResponse} that reads the request's body into memory,
|
||||
* thus allowing for multiple invocations of {@link #getBody()}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
*/
|
||||
final class BufferingClientHttpResponseWrapper implements ClientHttpResponse {
|
||||
|
||||
private final ClientHttpResponse response;
|
||||
|
||||
private byte[] body;
|
||||
|
||||
|
||||
BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
|
||||
public HttpStatus getStatusCode() throws IOException {
|
||||
return this.response.getStatusCode();
|
||||
}
|
||||
|
||||
public int getRawStatusCode() throws IOException {
|
||||
return this.response.getRawStatusCode();
|
||||
}
|
||||
|
||||
public String getStatusText() throws IOException {
|
||||
return this.response.getStatusText();
|
||||
}
|
||||
|
||||
public HttpHeaders getHeaders() {
|
||||
return this.response.getHeaders();
|
||||
}
|
||||
|
||||
public InputStream getBody() throws IOException {
|
||||
if (this.body == null) {
|
||||
this.body = FileCopyUtils.copyToByteArray(this.response.getBody());
|
||||
}
|
||||
return new ByteArrayInputStream(this.body);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
this.response.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.HttpRequest;
|
||||
|
||||
/**
|
||||
* Represents a client-side HTTP request. Created via an implementation of the {@link ClientHttpRequestFactory}.
|
||||
*
|
||||
* <p>A <code>HttpRequest</code> can be {@linkplain #execute() executed}, getting a {@link ClientHttpResponse}
|
||||
* which can be read from.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
* @see ClientHttpRequestFactory#createRequest(java.net.URI, HttpMethod)
|
||||
*/
|
||||
public interface ClientHttpRequest extends HttpRequest, HttpOutputMessage {
|
||||
|
||||
/**
|
||||
* Execute this request, resulting in a {@link ClientHttpResponse} that can be read.
|
||||
* @return the response result of the execution
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
ClientHttpResponse execute() throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.http.HttpRequest;
|
||||
|
||||
/**
|
||||
* Represents the context of a client-side HTTP request execution.
|
||||
*
|
||||
* <p>Used to invoke the next interceptor in the interceptor chain, or - if the calling interceptor is last - execute
|
||||
* the request itself.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @see ClientHttpRequestInterceptor
|
||||
* @since 3.1
|
||||
*/
|
||||
public interface ClientHttpRequestExecution {
|
||||
|
||||
/**
|
||||
* Execute the request with the given request attributes and body, and return the response.
|
||||
*
|
||||
* @param request the request, containing method, URI, and headers
|
||||
* @param body the body of the request to execute
|
||||
* @return the response
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* Factory for {@link ClientHttpRequest} objects.
|
||||
* Requests are created by the {@link #createRequest(URI, HttpMethod)} method.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface ClientHttpRequestFactory {
|
||||
|
||||
/**
|
||||
* Create a new {@link ClientHttpRequest} for the specified URI and HTTP method.
|
||||
* <p>The returned request can be written to, and then executed by calling
|
||||
* {@link ClientHttpRequest#execute()}.
|
||||
* @param uri the URI to create a request for
|
||||
* @param httpMethod the HTTP method to execute
|
||||
* @return the created request
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.http.HttpRequest;
|
||||
|
||||
/**
|
||||
* Intercepts client-side HTTP requests. Implementations of this interface can be {@linkplain
|
||||
* org.springframework.web.client.RestTemplate#setInterceptors(ClientHttpRequestInterceptor[]) registered} with the
|
||||
* {@link org.springframework.web.client.RestTemplate RestTemplate}, as to modify the outgoing {@link ClientHttpRequest}
|
||||
* and/or the incoming {@link ClientHttpResponse}.
|
||||
*
|
||||
* <p>The main entry point for interceptors is {@link #intercept(HttpRequest, byte[], ClientHttpRequestExecution)}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
*/
|
||||
public interface ClientHttpRequestInterceptor {
|
||||
|
||||
/**
|
||||
* Intercept the given request, and return a response. The given {@link ClientHttpRequestExecution} allows
|
||||
* the interceptor to pass on the request and response to the next entity in the chain.
|
||||
*
|
||||
* <p>A typical implementation of this method would follow the following pattern:
|
||||
* <ol>
|
||||
* <li>Examine the {@linkplain HttpRequest request} and body</li>
|
||||
* <li>Optionally {@linkplain org.springframework.http.client.support.HttpRequestWrapper wrap} the request to filter HTTP attributes.</li>
|
||||
* <li>Optionally modify the body of the request.</li>
|
||||
* <li><strong>Either</strong>
|
||||
* <ul>
|
||||
* <li>execute the request using {@link ClientHttpRequestExecution#execute(org.springframework.http.HttpRequest, byte[])},</li>
|
||||
* <strong>or</strong>
|
||||
* <li>do not execute the request to block the execution altogether.</li>
|
||||
* </ul>
|
||||
* <li>Optionally wrap the response to filter HTTP attributes.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param request the request, containing method, URI, and headers
|
||||
* @param body the body of the request
|
||||
* @param execution the request execution
|
||||
* @return the response
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
|
||||
throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* Represents a client-side HTTP response. Obtained via an calling of the {@link ClientHttpRequest#execute()}.
|
||||
*
|
||||
* <p>A <code>ClientHttpResponse</code> must be {@linkplain #close() closed}, typically in a
|
||||
* <code>finally</code> block.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface ClientHttpResponse extends HttpInputMessage {
|
||||
|
||||
/**
|
||||
* Return the HTTP status code of the response.
|
||||
* @return the HTTP status as an HttpStatus enum value
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
HttpStatus getStatusCode() throws IOException;
|
||||
|
||||
/**
|
||||
* Return the HTTP status code of the response as integer
|
||||
* @return the HTTP status as an integer
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
int getRawStatusCode() throws IOException;
|
||||
|
||||
/**
|
||||
* Return the HTTP status text of the response.
|
||||
* @return the HTTP status text
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
String getStatusText() throws IOException;
|
||||
|
||||
/**
|
||||
* Closes this response, freeing any resources created.
|
||||
*/
|
||||
void close();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.httpclient.HttpClient;
|
||||
import org.apache.commons.httpclient.HttpMethodBase;
|
||||
import org.apache.commons.httpclient.URIException;
|
||||
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
|
||||
import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
|
||||
import org.apache.commons.httpclient.methods.RequestEntity;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.http.client.ClientHttpRequest} implementation that uses
|
||||
* Apache Commons HttpClient to execute requests.
|
||||
*
|
||||
* <p>Created via the {@link CommonsClientHttpRequestFactory}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
* @see CommonsClientHttpRequestFactory#createRequest(java.net.URI, HttpMethod)
|
||||
* @deprecated In favor of {@link HttpComponentsClientHttpRequest}
|
||||
*/
|
||||
@Deprecated
|
||||
final class CommonsClientHttpRequest extends AbstractBufferingClientHttpRequest {
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
private final HttpMethodBase httpMethod;
|
||||
|
||||
|
||||
CommonsClientHttpRequest(HttpClient httpClient, HttpMethodBase httpMethod) {
|
||||
this.httpClient = httpClient;
|
||||
this.httpMethod = httpMethod;
|
||||
}
|
||||
|
||||
|
||||
public HttpMethod getMethod() {
|
||||
return HttpMethod.valueOf(this.httpMethod.getName());
|
||||
}
|
||||
|
||||
public URI getURI() {
|
||||
try {
|
||||
return URI.create(this.httpMethod.getURI().getEscapedURI());
|
||||
}
|
||||
catch (URIException ex) {
|
||||
throw new IllegalStateException("Could not get HttpMethod URI: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse executeInternal(HttpHeaders headers, byte[] output) throws IOException {
|
||||
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
String headerName = entry.getKey();
|
||||
for (String headerValue : entry.getValue()) {
|
||||
httpMethod.addRequestHeader(headerName, headerValue);
|
||||
}
|
||||
}
|
||||
if (this.httpMethod instanceof EntityEnclosingMethod) {
|
||||
EntityEnclosingMethod entityEnclosingMethod = (EntityEnclosingMethod) this.httpMethod;
|
||||
RequestEntity requestEntity = new ByteArrayRequestEntity(output);
|
||||
entityEnclosingMethod.setRequestEntity(requestEntity);
|
||||
}
|
||||
this.httpClient.executeMethod(this.httpMethod);
|
||||
return new CommonsClientHttpResponse(this.httpMethod);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import org.apache.commons.httpclient.HttpClient;
|
||||
import org.apache.commons.httpclient.HttpConnectionManager;
|
||||
import org.apache.commons.httpclient.HttpMethodBase;
|
||||
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
|
||||
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.TraceMethod;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.http.client.ClientHttpRequestFactory} implementation that uses
|
||||
* <a href="http://jakarta.apache.org/commons/httpclient">Jakarta Commons HttpClient</a> to create requests.
|
||||
*
|
||||
* <p>Allows to use a pre-configured {@link HttpClient} instance -
|
||||
* potentially with authentication, HTTP connection pooling, etc.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
* @see org.springframework.http.client.SimpleClientHttpRequestFactory
|
||||
* @deprecated In favor of {@link HttpComponentsClientHttpRequestFactory}
|
||||
*/
|
||||
@Deprecated
|
||||
public class CommonsClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean {
|
||||
|
||||
private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);
|
||||
|
||||
private HttpClient httpClient;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance of the <code>CommonsHttpRequestFactory</code> with a default
|
||||
* {@link HttpClient} that uses a default {@link MultiThreadedHttpConnectionManager}.
|
||||
*/
|
||||
public CommonsClientHttpRequestFactory() {
|
||||
this.httpClient = new HttpClient(new MultiThreadedHttpConnectionManager());
|
||||
this.setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of the <code>CommonsHttpRequestFactory</code> with the given
|
||||
* {@link HttpClient} instance.
|
||||
* @param httpClient the HttpClient instance to use for this factory
|
||||
*/
|
||||
public CommonsClientHttpRequestFactory(HttpClient httpClient) {
|
||||
Assert.notNull(httpClient, "httpClient must not be null");
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the <code>HttpClient</code> used by this factory.
|
||||
*/
|
||||
public void setHttpClient(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the <code>HttpClient</code> used by this factory.
|
||||
*/
|
||||
public HttpClient getHttpClient() {
|
||||
return this.httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection timeout for the underlying HttpClient.
|
||||
* A timeout value of 0 specifies an infinite timeout.
|
||||
* @param timeout the timeout value in milliseconds
|
||||
* @see org.apache.commons.httpclient.params.HttpConnectionManagerParams#setConnectionTimeout(int)
|
||||
*/
|
||||
public void setConnectTimeout(int timeout) {
|
||||
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
|
||||
this.httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the socket read timeout for the underlying HttpClient.
|
||||
* A timeout value of 0 specifies an infinite timeout.
|
||||
* @param timeout the timeout value in milliseconds
|
||||
* @see org.apache.commons.httpclient.params.HttpConnectionManagerParams#setSoTimeout(int)
|
||||
*/
|
||||
public void setReadTimeout(int timeout) {
|
||||
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
|
||||
getHttpClient().getHttpConnectionManager().getParams().setSoTimeout(timeout);
|
||||
}
|
||||
|
||||
|
||||
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
|
||||
HttpMethodBase commonsHttpMethod = createCommonsHttpMethod(httpMethod, uri.toString());
|
||||
postProcessCommonsHttpMethod(commonsHttpMethod);
|
||||
return new CommonsClientHttpRequest(getHttpClient(), commonsHttpMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Commons HttpMethodBase object for the given HTTP method
|
||||
* and URI specification.
|
||||
* @param httpMethod the HTTP method
|
||||
* @param uri the URI
|
||||
* @return the Commons HttpMethodBase object
|
||||
*/
|
||||
protected HttpMethodBase createCommonsHttpMethod(HttpMethod httpMethod, String uri) {
|
||||
switch (httpMethod) {
|
||||
case GET:
|
||||
return new GetMethod(uri);
|
||||
case DELETE:
|
||||
return new DeleteMethod(uri);
|
||||
case HEAD:
|
||||
return new HeadMethod(uri);
|
||||
case OPTIONS:
|
||||
return new OptionsMethod(uri);
|
||||
case POST:
|
||||
return new PostMethod(uri);
|
||||
case PUT:
|
||||
return new PutMethod(uri);
|
||||
case TRACE:
|
||||
return new TraceMethod(uri);
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method that allows for manipulating the {@link org.apache.commons.httpclient.HttpMethodBase}
|
||||
* before it is returned as part of a {@link CommonsClientHttpRequest}.
|
||||
* <p>The default implementation is empty.
|
||||
* @param httpMethod the Commons HTTP method object to process
|
||||
*/
|
||||
protected void postProcessCommonsHttpMethod(HttpMethodBase httpMethod) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown hook that closes the underlying {@link HttpConnectionManager}'s
|
||||
* connection pool, if any.
|
||||
*/
|
||||
public void destroy() {
|
||||
HttpConnectionManager connectionManager = getHttpClient().getHttpConnectionManager();
|
||||
if (connectionManager instanceof MultiThreadedHttpConnectionManager) {
|
||||
((MultiThreadedHttpConnectionManager) connectionManager).shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.commons.httpclient.Header;
|
||||
import org.apache.commons.httpclient.HttpMethod;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.http.client.ClientHttpResponse} implementation that uses
|
||||
* Apache Commons HttpClient to execute requests.
|
||||
*
|
||||
* <p>Created via the {@link CommonsClientHttpRequest}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
* @see CommonsClientHttpRequest#execute()
|
||||
* @deprecated In favor of {@link HttpComponentsClientHttpResponse}
|
||||
*/
|
||||
@Deprecated
|
||||
final class CommonsClientHttpResponse extends AbstractClientHttpResponse {
|
||||
|
||||
private final HttpMethod httpMethod;
|
||||
|
||||
private HttpHeaders headers;
|
||||
|
||||
|
||||
CommonsClientHttpResponse(HttpMethod httpMethod) {
|
||||
this.httpMethod = httpMethod;
|
||||
}
|
||||
|
||||
|
||||
public int getRawStatusCode() {
|
||||
return this.httpMethod.getStatusCode();
|
||||
}
|
||||
|
||||
public String getStatusText() {
|
||||
return this.httpMethod.getStatusText();
|
||||
}
|
||||
|
||||
public HttpHeaders getHeaders() {
|
||||
if (this.headers == null) {
|
||||
this.headers = new HttpHeaders();
|
||||
for (Header header : this.httpMethod.getResponseHeaders()) {
|
||||
this.headers.add(header.getName(), header.getValue());
|
||||
}
|
||||
}
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
public InputStream getBody() throws IOException {
|
||||
return this.httpMethod.getResponseBodyAsStream();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
this.httpMethod.releaseConnection();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.apache.http.protocol.HTTP;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.http.client.ClientHttpRequest} implementation that uses
|
||||
* Apache HttpComponents HttpClient to execute requests.
|
||||
*
|
||||
* <p>Created via the {@link HttpComponentsClientHttpRequestFactory}.
|
||||
*
|
||||
* @author Oleg Kalnichevski
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
* @see HttpComponentsClientHttpRequestFactory#createRequest(URI, HttpMethod)
|
||||
*/
|
||||
final class HttpComponentsClientHttpRequest extends AbstractBufferingClientHttpRequest {
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
private final HttpUriRequest httpRequest;
|
||||
|
||||
private final HttpContext httpContext;
|
||||
|
||||
|
||||
public HttpComponentsClientHttpRequest(HttpClient httpClient, HttpUriRequest httpRequest, HttpContext httpContext) {
|
||||
this.httpClient = httpClient;
|
||||
this.httpRequest = httpRequest;
|
||||
this.httpContext = httpContext;
|
||||
}
|
||||
|
||||
|
||||
public HttpMethod getMethod() {
|
||||
return HttpMethod.valueOf(this.httpRequest.getMethod());
|
||||
}
|
||||
|
||||
public URI getURI() {
|
||||
return this.httpRequest.getURI();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
|
||||
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
String headerName = entry.getKey();
|
||||
if (!headerName.equalsIgnoreCase(HTTP.CONTENT_LEN) &&
|
||||
!headerName.equalsIgnoreCase(HTTP.TRANSFER_ENCODING)) {
|
||||
for (String headerValue : entry.getValue()) {
|
||||
this.httpRequest.addHeader(headerName, headerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.httpRequest instanceof HttpEntityEnclosingRequest) {
|
||||
HttpEntityEnclosingRequest entityEnclosingRequest = (HttpEntityEnclosingRequest) this.httpRequest;
|
||||
HttpEntity requestEntity = new ByteArrayEntity(bufferedOutput);
|
||||
entityEnclosingRequest.setEntity(requestEntity);
|
||||
}
|
||||
HttpResponse httpResponse = this.httpClient.execute(this.httpRequest, this.httpContext);
|
||||
return new HttpComponentsClientHttpResponse(httpResponse);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpDelete;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpHead;
|
||||
import org.apache.http.client.methods.HttpOptions;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.client.methods.HttpTrace;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||
import org.apache.http.conn.scheme.Scheme;
|
||||
import org.apache.http.conn.scheme.SchemeRegistry;
|
||||
import org.apache.http.conn.ssl.SSLSocketFactory;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||
import org.apache.http.params.CoreConnectionPNames;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.http.client.ClientHttpRequestFactory} implementation that uses
|
||||
* <a href="http://hc.apache.org/httpcomponents-client-ga/httpclient/">Apache HttpComponents HttpClient</a>
|
||||
* to create requests.
|
||||
*
|
||||
* <p>Allows to use a pre-configured {@link HttpClient} instance -
|
||||
* potentially with authentication, HTTP connection pooling, etc.
|
||||
*
|
||||
* @author Oleg Kalnichevski
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
*/
|
||||
public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean {
|
||||
|
||||
private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 100;
|
||||
|
||||
private static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 5;
|
||||
|
||||
private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);
|
||||
|
||||
private HttpClient httpClient;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance of the HttpComponentsClientHttpRequestFactory with a default
|
||||
* {@link HttpClient} that uses a default {@link org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager}.
|
||||
*/
|
||||
public HttpComponentsClientHttpRequestFactory() {
|
||||
SchemeRegistry schemeRegistry = new SchemeRegistry();
|
||||
schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
|
||||
schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));
|
||||
|
||||
ThreadSafeClientConnManager connectionManager = new ThreadSafeClientConnManager(schemeRegistry);
|
||||
connectionManager.setMaxTotal(DEFAULT_MAX_TOTAL_CONNECTIONS);
|
||||
connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTIONS_PER_ROUTE);
|
||||
|
||||
this.httpClient = new DefaultHttpClient(connectionManager);
|
||||
setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance of the HttpComponentsClientHttpRequestFactory
|
||||
* with the given {@link HttpClient} instance.
|
||||
* @param httpClient the HttpClient instance to use for this request factory
|
||||
*/
|
||||
public HttpComponentsClientHttpRequestFactory(HttpClient httpClient) {
|
||||
Assert.notNull(httpClient, "HttpClient must not be null");
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the {@code HttpClient} used by this factory.
|
||||
*/
|
||||
public void setHttpClient(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@code HttpClient} used by this factory.
|
||||
*/
|
||||
public HttpClient getHttpClient() {
|
||||
return this.httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection timeout for the underlying HttpClient.
|
||||
* A timeout value of 0 specifies an infinite timeout.
|
||||
* @param timeout the timeout value in milliseconds
|
||||
*/
|
||||
public void setConnectTimeout(int timeout) {
|
||||
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
|
||||
getHttpClient().getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the socket read timeout for the underlying HttpClient.
|
||||
* A timeout value of 0 specifies an infinite timeout.
|
||||
* @param timeout the timeout value in milliseconds
|
||||
*/
|
||||
public void setReadTimeout(int timeout) {
|
||||
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
|
||||
getHttpClient().getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, timeout);
|
||||
}
|
||||
|
||||
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
|
||||
HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri);
|
||||
postProcessHttpRequest(httpRequest);
|
||||
return new HttpComponentsClientHttpRequest(getHttpClient(), httpRequest, createHttpContext(httpMethod, uri));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Commons HttpMethodBase object for the given HTTP method and URI specification.
|
||||
* @param httpMethod the HTTP method
|
||||
* @param uri the URI
|
||||
* @return the Commons HttpMethodBase object
|
||||
*/
|
||||
protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) {
|
||||
switch (httpMethod) {
|
||||
case GET:
|
||||
return new HttpGet(uri);
|
||||
case DELETE:
|
||||
return new HttpDelete(uri);
|
||||
case HEAD:
|
||||
return new HttpHead(uri);
|
||||
case OPTIONS:
|
||||
return new HttpOptions(uri);
|
||||
case POST:
|
||||
return new HttpPost(uri);
|
||||
case PUT:
|
||||
return new HttpPut(uri);
|
||||
case TRACE:
|
||||
return new HttpTrace(uri);
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method that allows for manipulating the {@link HttpUriRequest} before it is
|
||||
* returned as part of a {@link HttpComponentsClientHttpRequest}.
|
||||
* <p>The default implementation is empty.
|
||||
* @param request the request to process
|
||||
*/
|
||||
protected void postProcessHttpRequest(HttpUriRequest request) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Template methods that creates a {@link HttpContext} for the given HTTP method and URI.
|
||||
* <p>The default implementation returns {@code null}.
|
||||
* @param httpMethod the HTTP method
|
||||
* @param uri the URI
|
||||
* @return the http context
|
||||
*/
|
||||
protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown hook that closes the underlying
|
||||
* {@link org.apache.http.conn.ClientConnectionManager ClientConnectionManager}'s
|
||||
* connection pool, if any.
|
||||
*/
|
||||
public void destroy() {
|
||||
getHttpClient().getConnectionManager().shutdown();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.http.client.ClientHttpResponse} implementation that uses
|
||||
* Apache HttpComponents HttpClient to execute requests.
|
||||
*
|
||||
* <p>Created via the {@link HttpComponentsClientHttpRequest}.
|
||||
*
|
||||
* @author Oleg Kalnichevski
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
* @see HttpComponentsClientHttpRequest#execute()
|
||||
*/
|
||||
final class HttpComponentsClientHttpResponse extends AbstractClientHttpResponse {
|
||||
|
||||
private final HttpResponse httpResponse;
|
||||
|
||||
private HttpHeaders headers;
|
||||
|
||||
|
||||
HttpComponentsClientHttpResponse(HttpResponse httpResponse) {
|
||||
this.httpResponse = httpResponse;
|
||||
}
|
||||
|
||||
|
||||
public int getRawStatusCode() throws IOException {
|
||||
return this.httpResponse.getStatusLine().getStatusCode();
|
||||
}
|
||||
|
||||
public String getStatusText() throws IOException {
|
||||
return this.httpResponse.getStatusLine().getReasonPhrase();
|
||||
}
|
||||
|
||||
public HttpHeaders getHeaders() {
|
||||
if (this.headers == null) {
|
||||
this.headers = new HttpHeaders();
|
||||
for (Header header : this.httpResponse.getAllHeaders()) {
|
||||
this.headers.add(header.getName(), header.getValue());
|
||||
}
|
||||
}
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
public InputStream getBody() throws IOException {
|
||||
HttpEntity entity = this.httpResponse.getEntity();
|
||||
return entity != null ? entity.getContent() : null;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
HttpEntity entity = this.httpResponse.getEntity();
|
||||
if (entity != null) {
|
||||
try {
|
||||
// Release underlying connection back to the connection manager
|
||||
EntityUtils.consume(entity);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* Wrapper for a {@link ClientHttpRequest} that has support for {@link ClientHttpRequestInterceptor}s.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
*/
|
||||
class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest {
|
||||
|
||||
private final ClientHttpRequestFactory requestFactory;
|
||||
|
||||
private final List<ClientHttpRequestInterceptor> interceptors;
|
||||
|
||||
private HttpMethod method;
|
||||
|
||||
private URI uri;
|
||||
|
||||
protected InterceptingClientHttpRequest(ClientHttpRequestFactory requestFactory,
|
||||
List<ClientHttpRequestInterceptor> interceptors,
|
||||
URI uri,
|
||||
HttpMethod method) {
|
||||
this.requestFactory = requestFactory;
|
||||
this.interceptors = interceptors;
|
||||
this.method = method;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public HttpMethod getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public URI getURI() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
|
||||
RequestExecution requestExecution = new RequestExecution();
|
||||
|
||||
return requestExecution.execute(this, bufferedOutput);
|
||||
}
|
||||
|
||||
private class RequestExecution implements ClientHttpRequestExecution {
|
||||
|
||||
private final Iterator<ClientHttpRequestInterceptor> iterator;
|
||||
|
||||
private RequestExecution() {
|
||||
this.iterator = interceptors.iterator();
|
||||
}
|
||||
|
||||
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
|
||||
if (iterator.hasNext()) {
|
||||
ClientHttpRequestInterceptor nextInterceptor = iterator.next();
|
||||
return nextInterceptor.intercept(request, body, this);
|
||||
}
|
||||
else {
|
||||
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod());
|
||||
|
||||
delegate.getHeaders().putAll(request.getHeaders());
|
||||
|
||||
if (body.length > 0) {
|
||||
FileCopyUtils.copy(body, delegate.getBody());
|
||||
}
|
||||
return delegate.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* Wrapper for a {@link ClientHttpRequestFactory} that has support for {@link ClientHttpRequestInterceptor}s.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
*/
|
||||
public class InterceptingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {
|
||||
|
||||
private final List<ClientHttpRequestInterceptor> interceptors;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the {@code InterceptingClientHttpRequestFactory} with the given parameters.
|
||||
*
|
||||
* @param requestFactory the request factory to wrap
|
||||
* @param interceptors the interceptors that are to be applied. Can be {@code null}.
|
||||
*/
|
||||
public InterceptingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory,
|
||||
List<ClientHttpRequestInterceptor> interceptors) {
|
||||
super(requestFactory);
|
||||
this.interceptors = interceptors != null ? interceptors : Collections.<ClientHttpRequestInterceptor>emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) {
|
||||
return new InterceptingClientHttpRequest(requestFactory, interceptors, uri, httpMethod);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* {@link ClientHttpRequest} implementation that uses standard J2SE facilities to execute buffered requests.
|
||||
* Created via the {@link SimpleClientHttpRequestFactory}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
* @see SimpleClientHttpRequestFactory#createRequest(java.net.URI, HttpMethod)
|
||||
*/
|
||||
final class SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttpRequest {
|
||||
|
||||
private final HttpURLConnection connection;
|
||||
|
||||
|
||||
SimpleBufferingClientHttpRequest(HttpURLConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
|
||||
public HttpMethod getMethod() {
|
||||
return HttpMethod.valueOf(this.connection.getRequestMethod());
|
||||
}
|
||||
|
||||
public URI getURI() {
|
||||
try {
|
||||
return this.connection.getURL().toURI();
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
throw new IllegalStateException("Could not get HttpURLConnection URI: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
|
||||
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
String headerName = entry.getKey();
|
||||
for (String headerValue : entry.getValue()) {
|
||||
this.connection.addRequestProperty(headerName, headerValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.connection.getDoOutput()) {
|
||||
this.connection.setFixedLengthStreamingMode(bufferedOutput.length);
|
||||
}
|
||||
this.connection.connect();
|
||||
if (this.connection.getDoOutput()) {
|
||||
FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
|
||||
}
|
||||
|
||||
return new SimpleClientHttpResponse(this.connection);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.Proxy;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ClientHttpRequestFactory} implementation that uses standard J2SE facilities.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see java.net.HttpURLConnection
|
||||
* @see CommonsClientHttpRequestFactory
|
||||
*/
|
||||
public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory {
|
||||
|
||||
private static final int DEFAULT_CHUNK_SIZE = 4096;
|
||||
|
||||
|
||||
private Proxy proxy;
|
||||
|
||||
private boolean bufferRequestBody = true;
|
||||
|
||||
private int chunkSize = DEFAULT_CHUNK_SIZE;
|
||||
|
||||
private int connectTimeout = -1;
|
||||
|
||||
private int readTimeout = -1;
|
||||
|
||||
|
||||
/**
|
||||
* Set the {@link Proxy} to use for this request factory.
|
||||
*/
|
||||
public void setProxy(Proxy proxy) {
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this request factory should buffer the {@linkplain ClientHttpRequest#getBody() request body}
|
||||
* internally.
|
||||
* <p>Default is {@code true}. When sending large amounts of data via POST or PUT, it is recommended
|
||||
* to change this property to {@code false}, so as not to run out of memory. This will result in a
|
||||
* {@link ClientHttpRequest} that either streams directly to the underlying {@link HttpURLConnection}
|
||||
* (if the {@link org.springframework.http.HttpHeaders#getContentLength() Content-Length} is known in advance),
|
||||
* or that will use "Chunked transfer encoding" (if the {@code Content-Length} is not known in advance).
|
||||
* @see #setChunkSize(int)
|
||||
* @see HttpURLConnection#setFixedLengthStreamingMode(int)
|
||||
*/
|
||||
public void setBufferRequestBody(boolean bufferRequestBody) {
|
||||
this.bufferRequestBody = bufferRequestBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of bytes to write in each chunk when not buffering request bodies locally.
|
||||
* <p>Note that this parameter is only used when {@link #setBufferRequestBody(boolean) bufferRequestBody} is set
|
||||
* to {@code false}, and the {@link org.springframework.http.HttpHeaders#getContentLength() Content-Length}
|
||||
* is not known in advance.
|
||||
* @see #setBufferRequestBody(boolean)
|
||||
*/
|
||||
public void setChunkSize(int chunkSize) {
|
||||
this.chunkSize = chunkSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the underlying URLConnection's connect timeout (in milliseconds).
|
||||
* A timeout value of 0 specifies an infinite timeout.
|
||||
* <p>Default is the system's default timeout.
|
||||
* @see URLConnection#setConnectTimeout(int)
|
||||
*/
|
||||
public void setConnectTimeout(int connectTimeout) {
|
||||
this.connectTimeout = connectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the underlying URLConnection's read timeout (in milliseconds).
|
||||
* A timeout value of 0 specifies an infinite timeout.
|
||||
* <p>Default is the system's default timeout.
|
||||
* @see URLConnection#setReadTimeout(int)
|
||||
*/
|
||||
public void setReadTimeout(int readTimeout) {
|
||||
this.readTimeout = readTimeout;
|
||||
}
|
||||
|
||||
|
||||
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
|
||||
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
|
||||
prepareConnection(connection, httpMethod.name());
|
||||
if (this.bufferRequestBody) {
|
||||
return new SimpleBufferingClientHttpRequest(connection);
|
||||
}
|
||||
else {
|
||||
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens and returns a connection to the given URL.
|
||||
* <p>The default implementation uses the given {@linkplain #setProxy(java.net.Proxy) proxy} -
|
||||
* if any - to open a connection.
|
||||
* @param url the URL to open a connection to
|
||||
* @param proxy the proxy to use, may be {@code null}
|
||||
* @return the opened connection
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
protected HttpURLConnection openConnection(URL url, Proxy proxy) throws IOException {
|
||||
URLConnection urlConnection = (proxy != null ? url.openConnection(proxy) : url.openConnection());
|
||||
Assert.isInstanceOf(HttpURLConnection.class, urlConnection);
|
||||
return (HttpURLConnection) urlConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method for preparing the given {@link HttpURLConnection}.
|
||||
* <p>The default implementation prepares the connection for input and output, and sets the HTTP method.
|
||||
* @param connection the connection to prepare
|
||||
* @param httpMethod the HTTP request method ({@code GET}, {@code POST}, etc.)
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
|
||||
if (this.connectTimeout >= 0) {
|
||||
connection.setConnectTimeout(this.connectTimeout);
|
||||
}
|
||||
if (this.readTimeout >= 0) {
|
||||
connection.setReadTimeout(this.readTimeout);
|
||||
}
|
||||
connection.setDoInput(true);
|
||||
if ("GET".equals(httpMethod)) {
|
||||
connection.setInstanceFollowRedirects(true);
|
||||
}
|
||||
else {
|
||||
connection.setInstanceFollowRedirects(false);
|
||||
}
|
||||
if ("PUT".equals(httpMethod) || "POST".equals(httpMethod)) {
|
||||
connection.setDoOutput(true);
|
||||
}
|
||||
else {
|
||||
connection.setDoOutput(false);
|
||||
}
|
||||
connection.setRequestMethod(httpMethod);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link ClientHttpResponse} implementation that uses standard J2SE facilities.
|
||||
* Obtained via {@link SimpleBufferingClientHttpRequest#execute()} and
|
||||
* {@link SimpleStreamingClientHttpRequest#execute()}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
final class SimpleClientHttpResponse extends AbstractClientHttpResponse {
|
||||
|
||||
private final HttpURLConnection connection;
|
||||
|
||||
private HttpHeaders headers;
|
||||
|
||||
|
||||
SimpleClientHttpResponse(HttpURLConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
|
||||
public int getRawStatusCode() throws IOException {
|
||||
return this.connection.getResponseCode();
|
||||
}
|
||||
|
||||
public String getStatusText() throws IOException {
|
||||
return this.connection.getResponseMessage();
|
||||
}
|
||||
|
||||
public HttpHeaders getHeaders() {
|
||||
if (this.headers == null) {
|
||||
this.headers = new HttpHeaders();
|
||||
// Header field 0 is the status line for most HttpURLConnections, but not on GAE
|
||||
String name = this.connection.getHeaderFieldKey(0);
|
||||
if (StringUtils.hasLength(name)) {
|
||||
this.headers.add(name, this.connection.getHeaderField(0));
|
||||
}
|
||||
int i = 1;
|
||||
while (true) {
|
||||
name = this.connection.getHeaderFieldKey(i);
|
||||
if (!StringUtils.hasLength(name)) {
|
||||
break;
|
||||
}
|
||||
this.headers.add(name, this.connection.getHeaderField(i));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
public InputStream getBody() throws IOException {
|
||||
InputStream errorStream = this.connection.getErrorStream();
|
||||
return (errorStream != null ? errorStream : this.connection.getInputStream());
|
||||
}
|
||||
|
||||
public void close() {
|
||||
this.connection.disconnect();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client;
|
||||
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* {@link ClientHttpRequest} implementation that uses standard J2SE facilities to execute streaming requests.
|
||||
* Created via the {@link SimpleClientHttpRequestFactory}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
* @see SimpleClientHttpRequestFactory#createRequest(java.net.URI, HttpMethod)
|
||||
*/
|
||||
final class SimpleStreamingClientHttpRequest extends AbstractClientHttpRequest {
|
||||
|
||||
private final HttpURLConnection connection;
|
||||
|
||||
private final int chunkSize;
|
||||
|
||||
private OutputStream body;
|
||||
|
||||
|
||||
SimpleStreamingClientHttpRequest(HttpURLConnection connection, int chunkSize) {
|
||||
this.connection = connection;
|
||||
this.chunkSize = chunkSize;
|
||||
}
|
||||
|
||||
public HttpMethod getMethod() {
|
||||
return HttpMethod.valueOf(this.connection.getRequestMethod());
|
||||
}
|
||||
|
||||
public URI getURI() {
|
||||
try {
|
||||
return this.connection.getURL().toURI();
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
throw new IllegalStateException("Could not get HttpURLConnection URI: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException {
|
||||
if (this.body == null) {
|
||||
int contentLength = (int) headers.getContentLength();
|
||||
if (contentLength >= 0) {
|
||||
this.connection.setFixedLengthStreamingMode(contentLength);
|
||||
}
|
||||
else {
|
||||
this.connection.setChunkedStreamingMode(this.chunkSize);
|
||||
}
|
||||
writeHeaders(headers);
|
||||
this.connection.connect();
|
||||
this.body = this.connection.getOutputStream();
|
||||
}
|
||||
return new NonClosingOutputStream(this.body);
|
||||
}
|
||||
|
||||
private void writeHeaders(HttpHeaders headers) {
|
||||
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
String headerName = entry.getKey();
|
||||
for (String headerValue : entry.getValue()) {
|
||||
this.connection.addRequestProperty(headerName, headerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
|
||||
try {
|
||||
if (this.body != null) {
|
||||
this.body.close();
|
||||
}
|
||||
else {
|
||||
writeHeaders(headers);
|
||||
this.connection.connect();
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
return new SimpleClientHttpResponse(this.connection);
|
||||
}
|
||||
|
||||
|
||||
private static class NonClosingOutputStream extends FilterOutputStream {
|
||||
|
||||
private NonClosingOutputStream(OutputStream out) {
|
||||
super(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
/**
|
||||
*
|
||||
* Contains an abstraction over client-side HTTP. This package
|
||||
* contains the <code>ClientHttpRequest</code> and
|
||||
* <code>ClientHttpResponse</code>, as well as a basic implementation of these
|
||||
* interfaces.
|
||||
*
|
||||
*/
|
||||
package org.springframework.http.client;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client.support;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
|
||||
/**
|
||||
* Base class for {@link org.springframework.web.client.RestTemplate}
|
||||
* and other HTTP accessing gateway helpers, defining common properties
|
||||
* such as the {@link ClientHttpRequestFactory} to operate on.
|
||||
*
|
||||
* <p>Not intended to be used directly. See {@link org.springframework.web.client.RestTemplate}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
* @see org.springframework.web.client.RestTemplate
|
||||
*/
|
||||
public abstract class HttpAccessor {
|
||||
|
||||
/**
|
||||
* Logger available to subclasses.
|
||||
*/
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
|
||||
|
||||
|
||||
/**
|
||||
* Set the request factory that this accessor uses for obtaining {@link ClientHttpRequest HttpRequests}.
|
||||
*/
|
||||
public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
|
||||
Assert.notNull(requestFactory, "'requestFactory' must not be null");
|
||||
this.requestFactory = requestFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the request factory that this accessor uses for obtaining {@link ClientHttpRequest HttpRequests}.
|
||||
*/
|
||||
public ClientHttpRequestFactory getRequestFactory() {
|
||||
return this.requestFactory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link ClientHttpRequest} via this template's {@link ClientHttpRequestFactory}.
|
||||
* @param url the URL to connect to
|
||||
* @param method the HTTP method to exectute (GET, POST, etc.)
|
||||
* @return the created request
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
|
||||
ClientHttpRequest request = getRequestFactory().createRequest(url, method);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Created " + method.name() + " request for \"" + url + "\"");
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client.support;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Provides a convenient implementation of the {@link HttpRequest} interface that can be overridden to adapt the
|
||||
* request. Methods default to calling through to the wrapped request object.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
*/
|
||||
public class HttpRequestWrapper implements HttpRequest {
|
||||
|
||||
private final HttpRequest request;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@code HttpRequest} wrapping the given request object.
|
||||
*
|
||||
* @param request the request object to be wrapped
|
||||
*/
|
||||
public HttpRequestWrapper(HttpRequest request) {
|
||||
Assert.notNull(request, "'request' must not be null");
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the wrapped request.
|
||||
*/
|
||||
public HttpRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method of the wrapped request.
|
||||
*/
|
||||
public HttpMethod getMethod() {
|
||||
return this.request.getMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI of the wrapped request.
|
||||
*/
|
||||
public URI getURI() {
|
||||
return this.request.getURI();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the headers of the wrapped request.
|
||||
*/
|
||||
public HttpHeaders getHeaders() {
|
||||
return this.request.getHeaders();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.InterceptingClientHttpRequestFactory;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Base class for {@link org.springframework.web.client.RestTemplate} and other HTTP accessing gateway helpers, adding
|
||||
* interceptor-related properties to {@link HttpAccessor}'s common properties.
|
||||
*
|
||||
* <p>Not intended to be used directly. See {@link org.springframework.web.client.RestTemplate}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public abstract class InterceptingHttpAccessor extends HttpAccessor {
|
||||
|
||||
private List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
|
||||
|
||||
/**
|
||||
* Sets the request interceptors that this accessor should use.
|
||||
*/
|
||||
public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
|
||||
this.interceptors = interceptors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the request interceptor that this accessor uses.
|
||||
*/
|
||||
public List<ClientHttpRequestInterceptor> getInterceptors() {
|
||||
return interceptors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHttpRequestFactory getRequestFactory() {
|
||||
ClientHttpRequestFactory delegate = super.getRequestFactory();
|
||||
if (!CollectionUtils.isEmpty(getInterceptors())) {
|
||||
return new InterceptingClientHttpRequestFactory(delegate, getInterceptors());
|
||||
}
|
||||
else {
|
||||
return delegate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.client.support;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link FactoryBean} that creates a {@link Proxy java.net.Proxy}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0.4
|
||||
* @see FactoryBean
|
||||
* @see Proxy
|
||||
*/
|
||||
public class ProxyFactoryBean implements FactoryBean<Proxy>, InitializingBean {
|
||||
|
||||
private Proxy.Type type = Proxy.Type.HTTP;
|
||||
|
||||
private String hostname;
|
||||
|
||||
private int port = -1;
|
||||
|
||||
private Proxy proxy;
|
||||
|
||||
/**
|
||||
* Sets the proxy type. Defaults to {@link Proxy.Type#HTTP}.
|
||||
*/
|
||||
public void setType(Proxy.Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the proxy host name.
|
||||
*/
|
||||
public void setHostname(String hostname) {
|
||||
this.hostname = hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the proxy port.
|
||||
*/
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws IllegalArgumentException {
|
||||
Assert.notNull(type, "'type' must not be null");
|
||||
Assert.hasLength(hostname, "'hostname' must not be empty");
|
||||
Assert.isTrue(port >= 0 && port <= 65535, "'port' out of range: " + port);
|
||||
|
||||
SocketAddress socketAddress = new InetSocketAddress(hostname, port);
|
||||
this.proxy = new Proxy(type, socketAddress);
|
||||
|
||||
}
|
||||
|
||||
public Proxy getObject() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
public Class<?> getObjectType() {
|
||||
return Proxy.class;
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
/**
|
||||
*
|
||||
* This package provides generic HTTP support classes,
|
||||
* to be used by higher-level classes like RestTemplate.
|
||||
*
|
||||
*/
|
||||
package org.springframework.http.client.support;
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract base class for most {@link HttpMessageConverter} implementations.
|
||||
*
|
||||
* <p>This base class adds support for setting supported {@code MediaTypes}, through the
|
||||
* {@link #setSupportedMediaTypes(List) supportedMediaTypes} bean property. It also adds
|
||||
* support for {@code Content-Type} and {@code Content-Length} when writing to output messages.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {
|
||||
|
||||
/** Logger available to subclasses */
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private List<MediaType> supportedMediaTypes = Collections.emptyList();
|
||||
|
||||
|
||||
/**
|
||||
* Construct an {@code AbstractHttpMessageConverter} with no supported media types.
|
||||
* @see #setSupportedMediaTypes
|
||||
*/
|
||||
protected AbstractHttpMessageConverter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an {@code AbstractHttpMessageConverter} with one supported media type.
|
||||
* @param supportedMediaType the supported media type
|
||||
*/
|
||||
protected AbstractHttpMessageConverter(MediaType supportedMediaType) {
|
||||
setSupportedMediaTypes(Collections.singletonList(supportedMediaType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an {@code AbstractHttpMessageConverter} with multiple supported media type.
|
||||
* @param supportedMediaTypes the supported media types
|
||||
*/
|
||||
protected AbstractHttpMessageConverter(MediaType... supportedMediaTypes) {
|
||||
setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the list of {@link MediaType} objects supported by this converter.
|
||||
*/
|
||||
public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) {
|
||||
Assert.notEmpty(supportedMediaTypes, "'supportedMediaTypes' must not be empty");
|
||||
this.supportedMediaTypes = new ArrayList<MediaType>(supportedMediaTypes);
|
||||
}
|
||||
|
||||
public List<MediaType> getSupportedMediaTypes() {
|
||||
return Collections.unmodifiableList(this.supportedMediaTypes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This implementation checks if the given class is {@linkplain #supports(Class) supported},
|
||||
* and if the {@linkplain #getSupportedMediaTypes() supported media types}
|
||||
* {@linkplain MediaType#includes(MediaType) include} the given media type.
|
||||
*/
|
||||
public boolean canRead(Class<?> clazz, MediaType mediaType) {
|
||||
return supports(clazz) && canRead(mediaType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any of the {@linkplain #setSupportedMediaTypes(List) supported media types}
|
||||
* include the given media type.
|
||||
* @param mediaType the media type to read, can be {@code null} if not specified.
|
||||
* Typically the value of a {@code Content-Type} header.
|
||||
* @return {@code true} if the supported media types include the media type,
|
||||
* or if the media type is {@code null}
|
||||
*/
|
||||
protected boolean canRead(MediaType mediaType) {
|
||||
if (mediaType == null) {
|
||||
return true;
|
||||
}
|
||||
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
|
||||
if (supportedMediaType.includes(mediaType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation checks if the given class is {@linkplain #supports(Class) supported},
|
||||
* and if the {@linkplain #getSupportedMediaTypes() supported media types}
|
||||
* {@linkplain MediaType#includes(MediaType) include} the given media type.
|
||||
*/
|
||||
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
|
||||
return supports(clazz) && canWrite(mediaType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the given media type includes any of the
|
||||
* {@linkplain #setSupportedMediaTypes(List) supported media types}.
|
||||
* @param mediaType the media type to write, can be {@code null} if not specified.
|
||||
* Typically the value of an {@code Accept} header.
|
||||
* @return {@code true} if the supported media types are compatible with the media type,
|
||||
* or if the media type is {@code null}
|
||||
*/
|
||||
protected boolean canWrite(MediaType mediaType) {
|
||||
if (mediaType == null || MediaType.ALL.equals(mediaType)) {
|
||||
return true;
|
||||
}
|
||||
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
|
||||
if (supportedMediaType.isCompatibleWith(mediaType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation simple delegates to {@link #readInternal(Class, HttpInputMessage)}.
|
||||
* Future implementations might add some default behavior, however.
|
||||
*/
|
||||
public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException {
|
||||
return readInternal(clazz, inputMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation delegates to {@link #getDefaultContentType(Object)} if a content
|
||||
* type was not provided, calls {@link #getContentLength}, and sets the corresponding headers
|
||||
* on the output message. It then calls {@link #writeInternal}.
|
||||
*/
|
||||
public final void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
|
||||
HttpHeaders headers = outputMessage.getHeaders();
|
||||
if (headers.getContentType() == null) {
|
||||
if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) {
|
||||
contentType = getDefaultContentType(t);
|
||||
}
|
||||
if (contentType != null) {
|
||||
headers.setContentType(contentType);
|
||||
}
|
||||
}
|
||||
if (headers.getContentLength() == -1) {
|
||||
Long contentLength = getContentLength(t, headers.getContentType());
|
||||
if (contentLength != null) {
|
||||
headers.setContentLength(contentLength);
|
||||
}
|
||||
}
|
||||
writeInternal(t, outputMessage);
|
||||
outputMessage.getBody().flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default content type for the given type. Called when {@link #write}
|
||||
* is invoked without a specified content type parameter.
|
||||
* <p>By default, this returns the first element of the
|
||||
* {@link #setSupportedMediaTypes(List) supportedMediaTypes} property, if any.
|
||||
* Can be overridden in subclasses.
|
||||
* @param t the type to return the content type for
|
||||
* @return the content type, or <code>null</code> if not known
|
||||
*/
|
||||
protected MediaType getDefaultContentType(T t) throws IOException {
|
||||
List<MediaType> mediaTypes = getSupportedMediaTypes();
|
||||
return (!mediaTypes.isEmpty() ? mediaTypes.get(0) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content length for the given type.
|
||||
* <p>By default, this returns {@code null}, meaning that the content length is unknown.
|
||||
* Can be overridden in subclasses.
|
||||
* @param t the type to return the content length for
|
||||
* @return the content length, or {@code null} if not known
|
||||
*/
|
||||
protected Long getContentLength(T t, MediaType contentType) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Indicates whether the given class is supported by this converter.
|
||||
* @param clazz the class to test for support
|
||||
* @return <code>true</code> if supported; <code>false</code> otherwise
|
||||
*/
|
||||
protected abstract boolean supports(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Abstract template method that reads the actualy object. Invoked from {@link #read}.
|
||||
* @param clazz the type of object to return
|
||||
* @param inputMessage the HTTP input message to read from
|
||||
* @return the converted object
|
||||
* @throws IOException in case of I/O errors
|
||||
* @throws HttpMessageNotReadableException in case of conversion errors
|
||||
*/
|
||||
protected abstract T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException;
|
||||
|
||||
/**
|
||||
* Abstract template method that writes the actual body. Invoked from {@link #write}.
|
||||
* @param t the object to write to the output message
|
||||
* @param outputMessage the message to write to
|
||||
* @throws IOException in case of I/O errors
|
||||
* @throws HttpMessageNotWritableException in case of conversion errors
|
||||
*/
|
||||
protected abstract void writeInternal(T t, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.stream.FileCacheImageInputStream;
|
||||
import javax.imageio.stream.FileCacheImageOutputStream;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import javax.imageio.stream.MemoryCacheImageInputStream;
|
||||
import javax.imageio.stream.MemoryCacheImageOutputStream;
|
||||
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link HttpMessageConverter} that can read and write {@link BufferedImage BufferedImages}.
|
||||
*
|
||||
* <p>By default, this converter can read all media types that are supported by the {@linkplain
|
||||
* ImageIO#getReaderMIMETypes() registered image readers}, and writes using the media type of the first available
|
||||
* {@linkplain javax.imageio.ImageIO#getWriterMIMETypes() registered image writer}. This behavior can be overriden by
|
||||
* setting the #setContentType(org.springframework.http.MediaType) contentType} properties.
|
||||
*
|
||||
* <p>If the {@link #setCacheDir(java.io.File) cacheDir} property is set to an existing directory, this converter will
|
||||
* cache image data.
|
||||
*
|
||||
* <p>The {@link #process(ImageReadParam)} and {@link #process(ImageWriteParam)} template methods allow subclasses to
|
||||
* override Image I/O parameters.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public class BufferedImageHttpMessageConverter implements HttpMessageConverter<BufferedImage> {
|
||||
|
||||
private final List<MediaType> readableMediaTypes = new ArrayList<MediaType>();
|
||||
|
||||
private MediaType defaultContentType;
|
||||
|
||||
private File cacheDir;
|
||||
|
||||
|
||||
public BufferedImageHttpMessageConverter() {
|
||||
String[] readerMediaTypes = ImageIO.getReaderMIMETypes();
|
||||
for (String mediaType : readerMediaTypes) {
|
||||
this.readableMediaTypes.add(MediaType.parseMediaType(mediaType));
|
||||
}
|
||||
|
||||
String[] writerMediaTypes = ImageIO.getWriterMIMETypes();
|
||||
if (writerMediaTypes.length > 0) {
|
||||
this.defaultContentType = MediaType.parseMediaType(writerMediaTypes[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default {@code Content-Type} to be used for writing.
|
||||
* @throws IllegalArgumentException if the given content type is not supported by the Java Image I/O API
|
||||
*/
|
||||
public void setDefaultContentType(MediaType defaultContentType) {
|
||||
Assert.notNull(defaultContentType, "'contentType' must not be null");
|
||||
Iterator<ImageWriter> imageWriters = ImageIO.getImageWritersByMIMEType(defaultContentType.toString());
|
||||
if (!imageWriters.hasNext()) {
|
||||
throw new IllegalArgumentException(
|
||||
"ContentType [" + defaultContentType + "] is not supported by the Java Image I/O API");
|
||||
}
|
||||
|
||||
this.defaultContentType = defaultContentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default {@code Content-Type} to be used for writing.
|
||||
* Called when {@link #write} is invoked without a specified content type parameter.
|
||||
*/
|
||||
public MediaType getDefaultContentType() {
|
||||
return this.defaultContentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cache directory. If this property is set to an existing directory,
|
||||
* this converter will cache image data.
|
||||
*/
|
||||
public void setCacheDir(File cacheDir) {
|
||||
Assert.notNull(cacheDir, "'cacheDir' must not be null");
|
||||
Assert.isTrue(cacheDir.isDirectory(), "'cacheDir' is not a directory");
|
||||
this.cacheDir = cacheDir;
|
||||
}
|
||||
|
||||
|
||||
public boolean canRead(Class<?> clazz, MediaType mediaType) {
|
||||
return (BufferedImage.class.equals(clazz) && isReadable(mediaType));
|
||||
}
|
||||
|
||||
private boolean isReadable(MediaType mediaType) {
|
||||
if (mediaType == null) {
|
||||
return true;
|
||||
}
|
||||
Iterator<ImageReader> imageReaders = ImageIO.getImageReadersByMIMEType(mediaType.toString());
|
||||
return imageReaders.hasNext();
|
||||
}
|
||||
|
||||
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
|
||||
return (BufferedImage.class.equals(clazz) && isWritable(mediaType));
|
||||
}
|
||||
|
||||
private boolean isWritable(MediaType mediaType) {
|
||||
if (mediaType == null) {
|
||||
return true;
|
||||
}
|
||||
Iterator<ImageWriter> imageWriters = ImageIO.getImageWritersByMIMEType(mediaType.toString());
|
||||
return imageWriters.hasNext();
|
||||
}
|
||||
|
||||
public List<MediaType> getSupportedMediaTypes() {
|
||||
return Collections.unmodifiableList(this.readableMediaTypes);
|
||||
}
|
||||
|
||||
public BufferedImage read(Class<? extends BufferedImage> clazz, HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
|
||||
ImageInputStream imageInputStream = null;
|
||||
ImageReader imageReader = null;
|
||||
try {
|
||||
imageInputStream = createImageInputStream(inputMessage.getBody());
|
||||
MediaType contentType = inputMessage.getHeaders().getContentType();
|
||||
Iterator<ImageReader> imageReaders = ImageIO.getImageReadersByMIMEType(contentType.toString());
|
||||
if (imageReaders.hasNext()) {
|
||||
imageReader = imageReaders.next();
|
||||
ImageReadParam irp = imageReader.getDefaultReadParam();
|
||||
process(irp);
|
||||
imageReader.setInput(imageInputStream, true);
|
||||
return imageReader.read(0, irp);
|
||||
}
|
||||
else {
|
||||
throw new HttpMessageNotReadableException(
|
||||
"Could not find javax.imageio.ImageReader for Content-Type [" + contentType + "]");
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (imageReader != null) {
|
||||
imageReader.dispose();
|
||||
}
|
||||
if (imageInputStream != null) {
|
||||
try {
|
||||
imageInputStream.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ImageInputStream createImageInputStream(InputStream is) throws IOException {
|
||||
if (this.cacheDir != null) {
|
||||
return new FileCacheImageInputStream(is, cacheDir);
|
||||
}
|
||||
else {
|
||||
return new MemoryCacheImageInputStream(is);
|
||||
}
|
||||
}
|
||||
|
||||
public void write(BufferedImage image, MediaType contentType, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
|
||||
if (contentType == null) {
|
||||
contentType = getDefaultContentType();
|
||||
}
|
||||
Assert.notNull(contentType,
|
||||
"Count not determine Content-Type, set one using the 'defaultContentType' property");
|
||||
outputMessage.getHeaders().setContentType(contentType);
|
||||
ImageOutputStream imageOutputStream = null;
|
||||
ImageWriter imageWriter = null;
|
||||
try {
|
||||
imageOutputStream = createImageOutputStream(outputMessage.getBody());
|
||||
Iterator<ImageWriter> imageWriters = ImageIO.getImageWritersByMIMEType(contentType.toString());
|
||||
if (imageWriters.hasNext()) {
|
||||
imageWriter = imageWriters.next();
|
||||
ImageWriteParam iwp = imageWriter.getDefaultWriteParam();
|
||||
process(iwp);
|
||||
imageWriter.setOutput(imageOutputStream);
|
||||
imageWriter.write(null, new IIOImage(image, null, null), iwp);
|
||||
}
|
||||
else {
|
||||
throw new HttpMessageNotWritableException(
|
||||
"Could not find javax.imageio.ImageWriter for Content-Type [" + contentType + "]");
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (imageWriter != null) {
|
||||
imageWriter.dispose();
|
||||
}
|
||||
if (imageOutputStream != null) {
|
||||
try {
|
||||
imageOutputStream.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ImageOutputStream createImageOutputStream(OutputStream os) throws IOException {
|
||||
if (this.cacheDir != null) {
|
||||
return new FileCacheImageOutputStream(os, this.cacheDir);
|
||||
}
|
||||
else {
|
||||
return new MemoryCacheImageOutputStream(os);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Template method that allows for manipulating the {@link ImageReadParam} before it is used to read an image.
|
||||
* <p>Default implementation is empty.
|
||||
*/
|
||||
protected void process(ImageReadParam irp) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method that allows for manipulating the {@link ImageWriteParam} before it is used to write an image.
|
||||
* <p>Default implementation is empty.
|
||||
*/
|
||||
protected void process(ImageWriteParam iwp) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* Implementation of {@link HttpMessageConverter} that can read and write byte arrays.
|
||||
*
|
||||
* <p>By default, this converter supports all media types (<code>*/*</code>), and writes with a {@code
|
||||
* Content-Type} of {@code application/octet-stream}. This can be overridden by setting the {@link
|
||||
* #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public class ByteArrayHttpMessageConverter extends AbstractHttpMessageConverter<byte[]> {
|
||||
|
||||
/** Creates a new instance of the {@code ByteArrayHttpMessageConverter}. */
|
||||
public ByteArrayHttpMessageConverter() {
|
||||
super(new MediaType("application", "octet-stream"), MediaType.ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return byte[].class.equals(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException {
|
||||
long contentLength = inputMessage.getHeaders().getContentLength();
|
||||
if (contentLength >= 0) {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream((int) contentLength);
|
||||
FileCopyUtils.copy(inputMessage.getBody(), bos);
|
||||
return bos.toByteArray();
|
||||
}
|
||||
else {
|
||||
return FileCopyUtils.copyToByteArray(inputMessage.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Long getContentLength(byte[] bytes, MediaType contentType) {
|
||||
return (long) bytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(byte[] bytes, HttpOutputMessage outputMessage) throws IOException {
|
||||
FileCopyUtils.copy(bytes, outputMessage.getBody());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Implementation of {@link HttpMessageConverter} that can handle form data, including multipart form data (i.e. file
|
||||
* uploads).
|
||||
*
|
||||
* <p>This converter can write the {@code application/x-www-form-urlencoded} and {@code multipart/form-data} media
|
||||
* types, and read the {@code application/x-www-form-urlencoded}) media type (but not {@code multipart/form-data}).
|
||||
*
|
||||
* <p>In other words, this converter can read and write 'normal' HTML forms (as {@link MultiValueMap
|
||||
* MultiValueMap<String, String>}), and it can write multipart form (as {@link MultiValueMap
|
||||
* MultiValueMap<String, Object>}. When writing multipart, this converter uses other {@link HttpMessageConverter
|
||||
* HttpMessageConverters} to write the respective MIME parts. By default, basic converters are registered (supporting
|
||||
* {@code Strings} and {@code Resources}, for instance); these can be overridden by setting the {@link
|
||||
* #setPartConverters(java.util.List) partConverters} property.
|
||||
*
|
||||
* <p>For example, the following snippet shows how to submit an HTML form: <pre class="code"> RestTemplate template =
|
||||
* new RestTemplate(); // FormHttpMessageConverter is configured by default MultiValueMap<String, String> form =
|
||||
* new LinkedMultiValueMap<String, String>(); form.add("field 1", "value 1"); form.add("field 2", "value 2");
|
||||
* form.add("field 2", "value 3"); template.postForLocation("http://example.com/myForm", form); </pre> <p>The following
|
||||
* snippet shows how to do a file upload: <pre class="code"> MultiValueMap<String, Object> parts = new
|
||||
* LinkedMultiValueMap<String, Object>(); parts.add("field 1", "value 1"); parts.add("file", new
|
||||
* ClassPathResource("myFile.jpg")); template.postForLocation("http://example.com/myFileUpload", parts); </pre>
|
||||
*
|
||||
* <p>Some methods in this class were inspired by {@link org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @see MultiValueMap
|
||||
* @since 3.0
|
||||
*/
|
||||
public class FormHttpMessageConverter implements HttpMessageConverter<MultiValueMap<String, ?>> {
|
||||
|
||||
private static final byte[] BOUNDARY_CHARS =
|
||||
new byte[]{'-', '_', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
|
||||
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A',
|
||||
'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
|
||||
'V', 'W', 'X', 'Y', 'Z'};
|
||||
|
||||
private final Random rnd = new Random();
|
||||
|
||||
private Charset charset = Charset.forName("UTF-8");
|
||||
|
||||
private List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();
|
||||
|
||||
private List<HttpMessageConverter<?>> partConverters = new ArrayList<HttpMessageConverter<?>>();
|
||||
|
||||
|
||||
public FormHttpMessageConverter() {
|
||||
this.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
|
||||
|
||||
this.partConverters.add(new ByteArrayHttpMessageConverter());
|
||||
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
|
||||
stringHttpMessageConverter.setWriteAcceptCharset(false);
|
||||
this.partConverters.add(stringHttpMessageConverter);
|
||||
this.partConverters.add(new ResourceHttpMessageConverter());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the message body converters to use. These converters are used to convert objects to MIME parts.
|
||||
*/
|
||||
public final void setPartConverters(List<HttpMessageConverter<?>> partConverters) {
|
||||
Assert.notEmpty(partConverters, "'partConverters' must not be empty");
|
||||
this.partConverters = partConverters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a message body converter. Such a converters is used to convert objects to MIME parts.
|
||||
*/
|
||||
public final void addPartConverter(HttpMessageConverter<?> partConverter) {
|
||||
Assert.notNull(partConverter, "'partConverter' must not be NULL");
|
||||
this.partConverters.add(partConverter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the character set used for writing form data.
|
||||
*/
|
||||
public void setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
public boolean canRead(Class<?> clazz, MediaType mediaType) {
|
||||
if (!MultiValueMap.class.isAssignableFrom(clazz)) {
|
||||
return false;
|
||||
}
|
||||
if (mediaType == null) {
|
||||
return true;
|
||||
}
|
||||
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
|
||||
// we can't read multipart
|
||||
if (!supportedMediaType.equals(MediaType.MULTIPART_FORM_DATA) &&
|
||||
supportedMediaType.includes(mediaType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
|
||||
if (!MultiValueMap.class.isAssignableFrom(clazz)) {
|
||||
return false;
|
||||
}
|
||||
if (mediaType == null || MediaType.ALL.equals(mediaType)) {
|
||||
return true;
|
||||
}
|
||||
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
|
||||
if (supportedMediaType.isCompatibleWith(mediaType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of {@link MediaType} objects supported by this converter.
|
||||
*/
|
||||
public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) {
|
||||
this.supportedMediaTypes = supportedMediaTypes;
|
||||
}
|
||||
|
||||
public List<MediaType> getSupportedMediaTypes() {
|
||||
return Collections.unmodifiableList(this.supportedMediaTypes);
|
||||
}
|
||||
|
||||
public MultiValueMap<String, String> read(Class<? extends MultiValueMap<String, ?>> clazz,
|
||||
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
|
||||
|
||||
MediaType contentType = inputMessage.getHeaders().getContentType();
|
||||
Charset charset = contentType.getCharSet() != null ? contentType.getCharSet() : this.charset;
|
||||
String body = FileCopyUtils.copyToString(new InputStreamReader(inputMessage.getBody(), charset));
|
||||
|
||||
String[] pairs = StringUtils.tokenizeToStringArray(body, "&");
|
||||
|
||||
MultiValueMap<String, String> result = new LinkedMultiValueMap<String, String>(pairs.length);
|
||||
|
||||
for (String pair : pairs) {
|
||||
int idx = pair.indexOf('=');
|
||||
if (idx == -1) {
|
||||
result.add(URLDecoder.decode(pair, charset.name()), null);
|
||||
}
|
||||
else {
|
||||
String name = URLDecoder.decode(pair.substring(0, idx), charset.name());
|
||||
String value = URLDecoder.decode(pair.substring(idx + 1), charset.name());
|
||||
result.add(name, value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void write(MultiValueMap<String, ?> map, MediaType contentType, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
if (!isMultipart(map, contentType)) {
|
||||
writeForm((MultiValueMap<String, String>) map, contentType, outputMessage);
|
||||
}
|
||||
else {
|
||||
writeMultipart((MultiValueMap<String, Object>) map, outputMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMultipart(MultiValueMap<String, ?> map, MediaType contentType) {
|
||||
if (contentType != null) {
|
||||
return MediaType.MULTIPART_FORM_DATA.equals(contentType);
|
||||
}
|
||||
for (String name : map.keySet()) {
|
||||
for (Object value : map.get(name)) {
|
||||
if (value != null && !(value instanceof String)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void writeForm(MultiValueMap<String, String> form, MediaType contentType, HttpOutputMessage outputMessage)
|
||||
throws IOException {
|
||||
Charset charset;
|
||||
if (contentType != null) {
|
||||
outputMessage.getHeaders().setContentType(contentType);
|
||||
charset = contentType.getCharSet() != null ? contentType.getCharSet() : this.charset;
|
||||
}
|
||||
else {
|
||||
outputMessage.getHeaders().setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
charset = this.charset;
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext();) {
|
||||
String name = nameIterator.next();
|
||||
for (Iterator<String> valueIterator = form.get(name).iterator(); valueIterator.hasNext();) {
|
||||
String value = valueIterator.next();
|
||||
builder.append(URLEncoder.encode(name, charset.name()));
|
||||
if (value != null) {
|
||||
builder.append('=');
|
||||
builder.append(URLEncoder.encode(value, charset.name()));
|
||||
if (valueIterator.hasNext()) {
|
||||
builder.append('&');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nameIterator.hasNext()) {
|
||||
builder.append('&');
|
||||
}
|
||||
}
|
||||
byte[] bytes = builder.toString().getBytes(charset.name());
|
||||
outputMessage.getHeaders().setContentLength(bytes.length);
|
||||
FileCopyUtils.copy(bytes, outputMessage.getBody());
|
||||
}
|
||||
|
||||
private void writeMultipart(MultiValueMap<String, Object> parts, HttpOutputMessage outputMessage)
|
||||
throws IOException {
|
||||
byte[] boundary = generateMultipartBoundary();
|
||||
|
||||
Map<String, String> parameters = Collections.singletonMap("boundary", new String(boundary, "US-ASCII"));
|
||||
MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters);
|
||||
outputMessage.getHeaders().setContentType(contentType);
|
||||
|
||||
writeParts(outputMessage.getBody(), parts, boundary);
|
||||
writeEnd(boundary, outputMessage.getBody());
|
||||
}
|
||||
|
||||
private void writeParts(OutputStream os, MultiValueMap<String, Object> parts, byte[] boundary) throws IOException {
|
||||
for (Map.Entry<String, List<Object>> entry : parts.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
for (Object part : entry.getValue()) {
|
||||
writeBoundary(boundary, os);
|
||||
HttpEntity entity = getEntity(part);
|
||||
writePart(name, entity, os);
|
||||
writeNewLine(os);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeBoundary(byte[] boundary, OutputStream os) throws IOException {
|
||||
os.write('-');
|
||||
os.write('-');
|
||||
os.write(boundary);
|
||||
writeNewLine(os);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private HttpEntity getEntity(Object part) {
|
||||
if (part instanceof HttpEntity) {
|
||||
return (HttpEntity) part;
|
||||
}
|
||||
else {
|
||||
return new HttpEntity(part);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void writePart(String name, HttpEntity partEntity, OutputStream os) throws IOException {
|
||||
Object partBody = partEntity.getBody();
|
||||
Class<?> partType = partBody.getClass();
|
||||
HttpHeaders partHeaders = partEntity.getHeaders();
|
||||
MediaType partContentType = partHeaders.getContentType();
|
||||
for (HttpMessageConverter messageConverter : partConverters) {
|
||||
if (messageConverter.canWrite(partType, partContentType)) {
|
||||
HttpOutputMessage multipartOutputMessage = new MultipartHttpOutputMessage(os);
|
||||
multipartOutputMessage.getHeaders().setContentDispositionFormData(name, getFilename(partBody));
|
||||
if (!partHeaders.isEmpty()) {
|
||||
multipartOutputMessage.getHeaders().putAll(partHeaders);
|
||||
}
|
||||
messageConverter.write(partBody, partContentType, multipartOutputMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new HttpMessageNotWritableException(
|
||||
"Could not write request: no suitable HttpMessageConverter found for request type [" +
|
||||
partType.getName() + "]");
|
||||
}
|
||||
|
||||
private void writeEnd(byte[] boundary, OutputStream os) throws IOException {
|
||||
os.write('-');
|
||||
os.write('-');
|
||||
os.write(boundary);
|
||||
os.write('-');
|
||||
os.write('-');
|
||||
writeNewLine(os);
|
||||
}
|
||||
|
||||
private void writeNewLine(OutputStream os) throws IOException {
|
||||
os.write('\r');
|
||||
os.write('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a multipart boundary.
|
||||
* <p>The default implementation returns a random boundary.
|
||||
* Can be overridden in subclasses.
|
||||
*/
|
||||
protected byte[] generateMultipartBoundary() {
|
||||
byte[] boundary = new byte[rnd.nextInt(11) + 30];
|
||||
for (int i = 0; i < boundary.length; i++) {
|
||||
boundary[i] = BOUNDARY_CHARS[rnd.nextInt(BOUNDARY_CHARS.length)];
|
||||
}
|
||||
return boundary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the filename of the given multipart part. This value will be used for the
|
||||
* {@code Content-Disposition} header.
|
||||
* <p>The default implementation returns {@link Resource#getFilename()} if the part is a
|
||||
* {@code Resource}, and {@code null} in other cases. Can be overridden in subclasses.
|
||||
* @param part the part to determine the file name for
|
||||
* @return the filename, or {@code null} if not known
|
||||
*/
|
||||
protected String getFilename(Object part) {
|
||||
if (part instanceof Resource) {
|
||||
Resource resource = (Resource) part;
|
||||
return resource.getFilename();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.http.HttpOutputMessage} used for writing multipart data.
|
||||
*/
|
||||
private class MultipartHttpOutputMessage implements HttpOutputMessage {
|
||||
|
||||
private final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
private final OutputStream os;
|
||||
|
||||
private boolean headersWritten = false;
|
||||
|
||||
public MultipartHttpOutputMessage(OutputStream os) {
|
||||
this.os = os;
|
||||
}
|
||||
|
||||
public HttpHeaders getHeaders() {
|
||||
return headersWritten ? HttpHeaders.readOnlyHttpHeaders(headers) : this.headers;
|
||||
}
|
||||
|
||||
public OutputStream getBody() throws IOException {
|
||||
writeHeaders();
|
||||
return this.os;
|
||||
}
|
||||
|
||||
private void writeHeaders() throws IOException {
|
||||
if (!this.headersWritten) {
|
||||
for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
|
||||
byte[] headerName = getAsciiBytes(entry.getKey());
|
||||
for (String headerValueString : entry.getValue()) {
|
||||
byte[] headerValue = getAsciiBytes(headerValueString);
|
||||
os.write(headerName);
|
||||
os.write(':');
|
||||
os.write(' ');
|
||||
os.write(headerValue);
|
||||
writeNewLine(os);
|
||||
}
|
||||
}
|
||||
writeNewLine(os);
|
||||
this.headersWritten = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] getAsciiBytes(String name) {
|
||||
try {
|
||||
return name.getBytes("US-ASCII");
|
||||
}
|
||||
catch (UnsupportedEncodingException ex) {
|
||||
// should not happen, US-ASCII is always supported
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter;
|
||||
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
|
||||
/**
|
||||
* Thrown by {@link HttpMessageConverter} implementations when the conversion fails.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public class HttpMessageConversionException extends NestedRuntimeException {
|
||||
|
||||
/**
|
||||
* Create a new HttpMessageConversionException.
|
||||
*
|
||||
* @param msg the detail message
|
||||
*/
|
||||
public HttpMessageConversionException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new HttpMessageConversionException.
|
||||
*
|
||||
* @param msg the detail message
|
||||
* @param cause the root cause (if any)
|
||||
*/
|
||||
public HttpMessageConversionException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* Strategy interface that specifies a converter that can convert from and to HTTP requests and responses.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface HttpMessageConverter<T> {
|
||||
|
||||
/**
|
||||
* Indicates whether the given class can be read by this converter.
|
||||
* @param clazz the class to test for readability
|
||||
* @param mediaType the media type to read, can be {@code null} if not specified.
|
||||
* Typically the value of a {@code Content-Type} header.
|
||||
* @return {@code true} if readable; {@code false} otherwise
|
||||
*/
|
||||
boolean canRead(Class<?> clazz, MediaType mediaType);
|
||||
|
||||
/**
|
||||
* Indicates whether the given class can be written by this converter.
|
||||
* @param clazz the class to test for writability
|
||||
* @param mediaType the media type to write, can be {@code null} if not specified.
|
||||
* Typically the value of an {@code Accept} header.
|
||||
* @return {@code true} if writable; {@code false} otherwise
|
||||
*/
|
||||
boolean canWrite(Class<?> clazz, MediaType mediaType);
|
||||
|
||||
/**
|
||||
* Return the list of {@link MediaType} objects supported by this converter.
|
||||
* @return the list of supported media types
|
||||
*/
|
||||
List<MediaType> getSupportedMediaTypes();
|
||||
|
||||
/**
|
||||
* Read an object of the given type form the given input message, and returns it.
|
||||
* @param clazz the type of object to return. This type must have previously been passed to the
|
||||
* {@link #canRead canRead} method of this interface, which must have returned {@code true}.
|
||||
* @param inputMessage the HTTP input message to read from
|
||||
* @return the converted object
|
||||
* @throws IOException in case of I/O errors
|
||||
* @throws HttpMessageNotReadableException in case of conversion errors
|
||||
*/
|
||||
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException;
|
||||
|
||||
/**
|
||||
* Write an given object to the given output message.
|
||||
* @param t the object to write to the output message. The type of this object must have previously been
|
||||
* passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
|
||||
* @param contentType the content type to use when writing. May be {@code null} to indicate that the
|
||||
* default content type of the converter must be used. If not {@code null}, this media type must have
|
||||
* previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
|
||||
* returned {@code true}.
|
||||
* @param outputMessage the message to write to
|
||||
* @throws IOException in case of I/O errors
|
||||
* @throws HttpMessageNotWritableException in case of conversion errors
|
||||
*/
|
||||
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter;
|
||||
|
||||
/**
|
||||
* Thrown by {@link HttpMessageConverter} implementations when the
|
||||
* {@link HttpMessageConverter#read(Class, org.springframework.http.HttpInputMessage) read} method fails.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public class HttpMessageNotReadableException extends HttpMessageConversionException {
|
||||
|
||||
/**
|
||||
* Create a new HttpMessageNotReadableException.
|
||||
*
|
||||
* @param msg the detail message
|
||||
*/
|
||||
public HttpMessageNotReadableException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new HttpMessageNotReadableException.
|
||||
*
|
||||
* @param msg the detail message
|
||||
* @param cause the root cause (if any)
|
||||
*/
|
||||
public HttpMessageNotReadableException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter;
|
||||
|
||||
/**
|
||||
* Thrown by {@link org.springframework.http.converter.HttpMessageConverter} implementations when the
|
||||
* {@link org.springframework.http.converter.HttpMessageConverter#write(Object, org.springframework.http.HttpOutputMessage) write} method fails.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public class HttpMessageNotWritableException extends HttpMessageConversionException {
|
||||
|
||||
/**
|
||||
* Create a new HttpMessageNotWritableException.
|
||||
*
|
||||
* @param msg the detail message
|
||||
*/
|
||||
public HttpMessageNotWritableException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new HttpMessageNotWritableException.
|
||||
*
|
||||
* @param msg the detail message
|
||||
* @param cause the root cause (if any)
|
||||
*/
|
||||
public HttpMessageNotWritableException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import javax.activation.FileTypeMap;
|
||||
import javax.activation.MimetypesFileTypeMap;
|
||||
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Implementation of {@link HttpMessageConverter} that can read and write {@link Resource Resources}.
|
||||
*
|
||||
* <p>By default, this converter can read all media types. The Java Activation Framework (JAF) -
|
||||
* if available - is used to determine the {@code Content-Type} of written resources.
|
||||
* If JAF is not available, {@code application/octet-stream} is used.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0.2
|
||||
*/
|
||||
public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<Resource> {
|
||||
|
||||
private static final boolean jafPresent =
|
||||
ClassUtils.isPresent("javax.activation.FileTypeMap", ResourceHttpMessageConverter.class.getClassLoader());
|
||||
|
||||
|
||||
public ResourceHttpMessageConverter() {
|
||||
super(MediaType.ALL);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return Resource.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Resource readInternal(Class<? extends Resource> clazz, HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
|
||||
byte[] body = FileCopyUtils.copyToByteArray(inputMessage.getBody());
|
||||
return new ByteArrayResource(body);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MediaType getDefaultContentType(Resource resource) {
|
||||
if (jafPresent) {
|
||||
return ActivationMediaTypeFactory.getMediaType(resource);
|
||||
}
|
||||
else {
|
||||
return MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Long getContentLength(Resource resource, MediaType contentType) throws IOException {
|
||||
return resource.contentLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(Resource resource, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
|
||||
FileCopyUtils.copy(resource.getInputStream(), outputMessage.getBody());
|
||||
outputMessage.getBody().flush();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to avoid hard-coded JAF dependency.
|
||||
*/
|
||||
private static class ActivationMediaTypeFactory {
|
||||
|
||||
private static final FileTypeMap fileTypeMap;
|
||||
|
||||
static {
|
||||
fileTypeMap = loadFileTypeMapFromContextSupportModule();
|
||||
}
|
||||
|
||||
private static FileTypeMap loadFileTypeMapFromContextSupportModule() {
|
||||
// see if we can find the extended mime.types from the context-support module
|
||||
Resource mappingLocation = new ClassPathResource("org/springframework/mail/javamail/mime.types");
|
||||
if (mappingLocation.exists()) {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = mappingLocation.getInputStream();
|
||||
return new MimetypesFileTypeMap(inputStream);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return FileTypeMap.getDefaultFileTypeMap();
|
||||
}
|
||||
|
||||
public static MediaType getMediaType(Resource resource) {
|
||||
String mediaType = fileTypeMap.getContentType(resource.getFilename());
|
||||
return (StringUtils.hasText(mediaType) ? MediaType.parseMediaType(mediaType) : null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* Implementation of {@link HttpMessageConverter} that can read and write strings.
|
||||
*
|
||||
* <p>By default, this converter supports all media types (<code>*/*</code>), and writes with a {@code
|
||||
* Content-Type} of {@code text/plain}. This can be overridden by setting the {@link
|
||||
* #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
|
||||
|
||||
public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
|
||||
|
||||
private final List<Charset> availableCharsets;
|
||||
|
||||
private boolean writeAcceptCharset = true;
|
||||
|
||||
public StringHttpMessageConverter() {
|
||||
super(new MediaType("text", "plain", DEFAULT_CHARSET), MediaType.ALL);
|
||||
this.availableCharsets = new ArrayList<Charset>(Charset.availableCharsets().values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the {@code Accept-Charset} should be written to any outgoing request.
|
||||
* <p>Default is {@code true}.
|
||||
*/
|
||||
public void setWriteAcceptCharset(boolean writeAcceptCharset) {
|
||||
this.writeAcceptCharset = writeAcceptCharset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return String.class.equals(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException {
|
||||
Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
|
||||
return FileCopyUtils.copyToString(new InputStreamReader(inputMessage.getBody(), charset));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Long getContentLength(String s, MediaType contentType) {
|
||||
Charset charset = getContentTypeCharset(contentType);
|
||||
try {
|
||||
return (long) s.getBytes(charset.name()).length;
|
||||
}
|
||||
catch (UnsupportedEncodingException ex) {
|
||||
// should not occur
|
||||
throw new InternalError(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(String s, HttpOutputMessage outputMessage) throws IOException {
|
||||
if (writeAcceptCharset) {
|
||||
outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
|
||||
}
|
||||
Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
|
||||
FileCopyUtils.copy(s, new OutputStreamWriter(outputMessage.getBody(), charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of supported {@link Charset}.
|
||||
*
|
||||
* <p>By default, returns {@link Charset#availableCharsets()}. Can be overridden in subclasses.
|
||||
*
|
||||
* @return the list of accepted charsets
|
||||
*/
|
||||
protected List<Charset> getAcceptedCharsets() {
|
||||
return this.availableCharsets;
|
||||
}
|
||||
|
||||
private Charset getContentTypeCharset(MediaType contentType) {
|
||||
if (contentType != null && contentType.getCharSet() != null) {
|
||||
return contentType.getCharSet();
|
||||
}
|
||||
else {
|
||||
return DEFAULT_CHARSET;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter.feed;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import com.sun.syndication.feed.WireFeed;
|
||||
import com.sun.syndication.io.FeedException;
|
||||
import com.sun.syndication.io.WireFeedInput;
|
||||
import com.sun.syndication.io.WireFeedOutput;
|
||||
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Abstract base class for Atom and RSS Feed message converters, using java.net's
|
||||
* <a href="https://rome.dev.java.net/">ROME</a> package.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0.2
|
||||
* @see AtomFeedHttpMessageConverter
|
||||
* @see RssChannelHttpMessageConverter
|
||||
*/
|
||||
public abstract class AbstractWireFeedHttpMessageConverter<T extends WireFeed> extends AbstractHttpMessageConverter<T> {
|
||||
|
||||
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
|
||||
|
||||
protected AbstractWireFeedHttpMessageConverter(MediaType supportedMediaType) {
|
||||
super(supportedMediaType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
WireFeedInput feedInput = new WireFeedInput();
|
||||
MediaType contentType = inputMessage.getHeaders().getContentType();
|
||||
Charset charset;
|
||||
if (contentType != null && contentType.getCharSet() != null) {
|
||||
charset = contentType.getCharSet();
|
||||
} else {
|
||||
charset = DEFAULT_CHARSET;
|
||||
}
|
||||
try {
|
||||
Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
|
||||
return (T) feedInput.build(reader);
|
||||
}
|
||||
catch (FeedException ex) {
|
||||
throw new HttpMessageNotReadableException("Could not read WireFeed: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(T wireFeed, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
String wireFeedEncoding = wireFeed.getEncoding();
|
||||
if (!StringUtils.hasLength(wireFeedEncoding)) {
|
||||
wireFeedEncoding = DEFAULT_CHARSET.name();
|
||||
}
|
||||
MediaType contentType = outputMessage.getHeaders().getContentType();
|
||||
if (contentType != null) {
|
||||
Charset wireFeedCharset = Charset.forName(wireFeedEncoding);
|
||||
contentType = new MediaType(contentType.getType(), contentType.getSubtype(), wireFeedCharset);
|
||||
outputMessage.getHeaders().setContentType(contentType);
|
||||
}
|
||||
|
||||
WireFeedOutput feedOutput = new WireFeedOutput();
|
||||
|
||||
try {
|
||||
Writer writer = new OutputStreamWriter(outputMessage.getBody(), wireFeedEncoding);
|
||||
feedOutput.output(wireFeed, writer);
|
||||
}
|
||||
catch (FeedException ex) {
|
||||
throw new HttpMessageNotWritableException("Could not write WiredFeed: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter.feed;
|
||||
|
||||
import com.sun.syndication.feed.atom.Feed;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter} that can read and write Atom feeds.
|
||||
* Specifically, this converter can handle {@link Feed} objects, from the <a href="https://rome.dev.java.net/">ROME</a>
|
||||
* project.
|
||||
*
|
||||
* <p>By default, this converter reads and writes the media type ({@code application/atom+xml}). This can
|
||||
* be overridden by setting the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @see Feed
|
||||
* @since 3.0.2
|
||||
*/
|
||||
public class AtomFeedHttpMessageConverter extends AbstractWireFeedHttpMessageConverter<Feed> {
|
||||
|
||||
public AtomFeedHttpMessageConverter() {
|
||||
super(new MediaType("application", "atom+xml"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return Feed.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter.feed;
|
||||
|
||||
import com.sun.syndication.feed.rss.Channel;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter} that can read and write RSS feeds.
|
||||
* Specifically, this converter can handle {@link Channel} objects, from the <a href="https://rome.dev.java.net/">ROME</a>
|
||||
* project.
|
||||
*
|
||||
* <p>By default, this converter reads and writes the media type ({@code application/rss+xml}). This can
|
||||
* be overridden by setting the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @see Channel
|
||||
* @since 3.0.2
|
||||
*/
|
||||
public class RssChannelHttpMessageConverter extends AbstractWireFeedHttpMessageConverter<Channel> {
|
||||
|
||||
public RssChannelHttpMessageConverter() {
|
||||
super(new MediaType("application", "rss+xml"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return Channel.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* Provides HttpMessageConverter implementations for handling Atom and RSS feeds.
|
||||
*
|
||||
*/
|
||||
package org.springframework.http.converter.feed;
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
import org.codehaus.jackson.JsonEncoding;
|
||||
import org.codehaus.jackson.JsonGenerator;
|
||||
import org.codehaus.jackson.JsonProcessingException;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.map.type.TypeFactory;
|
||||
import org.codehaus.jackson.type.JavaType;
|
||||
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter}
|
||||
* that can read and write JSON using <a href="http://jackson.codehaus.org/">Jackson's</a> {@link ObjectMapper}.
|
||||
*
|
||||
* <p>This converter can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances.
|
||||
*
|
||||
* <p>By default, this converter supports {@code application/json}. This can be overridden by setting the
|
||||
* {@link #setSupportedMediaTypes(List) supportedMediaTypes} property.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
* @see org.springframework.web.servlet.view.json.MappingJacksonJsonView
|
||||
*/
|
||||
public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
|
||||
|
||||
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
|
||||
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private boolean prefixJson = false;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new {@code BindingJacksonHttpMessageConverter}.
|
||||
*/
|
||||
public MappingJacksonHttpMessageConverter() {
|
||||
super(new MediaType("application", "json", DEFAULT_CHARSET));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@code ObjectMapper} for this view. If not set, a default
|
||||
* {@link ObjectMapper#ObjectMapper() ObjectMapper} is used.
|
||||
* <p>Setting a custom-configured {@code ObjectMapper} is one way to take further control of the JSON
|
||||
* serialization process. For example, an extended {@link org.codehaus.jackson.map.SerializerFactory}
|
||||
* can be configured that provides custom serializers for specific types. The other option for refining
|
||||
* the serialization process is to use Jackson's provided annotations on the types to be serialized,
|
||||
* in which case a custom-configured ObjectMapper is unnecessary.
|
||||
*/
|
||||
public void setObjectMapper(ObjectMapper objectMapper) {
|
||||
Assert.notNull(objectMapper, "ObjectMapper must not be null");
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying {@code ObjectMapper} for this view.
|
||||
*/
|
||||
public ObjectMapper getObjectMapper() {
|
||||
return this.objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether the JSON output by this view should be prefixed with "{} &&". Default is false.
|
||||
* <p>Prefixing the JSON string in this manner is used to help prevent JSON Hijacking.
|
||||
* The prefix renders the string syntactically invalid as a script so that it cannot be hijacked.
|
||||
* This prefix does not affect the evaluation of JSON, but if JSON validation is performed on the
|
||||
* string, the prefix would need to be ignored.
|
||||
*/
|
||||
public void setPrefixJson(boolean prefixJson) {
|
||||
this.prefixJson = prefixJson;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canRead(Class<?> clazz, MediaType mediaType) {
|
||||
JavaType javaType = getJavaType(clazz);
|
||||
return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
|
||||
return (this.objectMapper.canSerialize(clazz) && canWrite(mediaType));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
// should not be called, since we override canRead/Write instead
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
|
||||
JavaType javaType = getJavaType(clazz);
|
||||
try {
|
||||
return this.objectMapper.readValue(inputMessage.getBody(), javaType);
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
|
||||
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
|
||||
JsonGenerator jsonGenerator =
|
||||
this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
|
||||
try {
|
||||
if (this.prefixJson) {
|
||||
jsonGenerator.writeRaw("{} && ");
|
||||
}
|
||||
this.objectMapper.writeValue(jsonGenerator, object);
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the Jackson {@link JavaType} for the specified class.
|
||||
* <p>The default implementation returns {@link TypeFactory#type(java.lang.reflect.Type)},
|
||||
* but this can be overridden in subclasses, to allow for custom generic collection handling.
|
||||
* For instance:
|
||||
* <pre class="code">
|
||||
* protected JavaType getJavaType(Class<?> clazz) {
|
||||
* if (List.class.isAssignableFrom(clazz)) {
|
||||
* return TypeFactory.collectionType(ArrayList.class, MyBean.class);
|
||||
* } else {
|
||||
* return super.getJavaType(clazz);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* @param clazz the class to return the java type for
|
||||
* @return the java type
|
||||
*/
|
||||
protected JavaType getJavaType(Class<?> clazz) {
|
||||
return TypeFactory.type(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the JSON encoding to use for the given content type.
|
||||
* @param contentType the media type as requested by the caller
|
||||
* @return the JSON encoding to use (never <code>null</code>)
|
||||
*/
|
||||
protected JsonEncoding getJsonEncoding(MediaType contentType) {
|
||||
if (contentType != null && contentType.getCharSet() != null) {
|
||||
Charset charset = contentType.getCharSet();
|
||||
for (JsonEncoding encoding : JsonEncoding.values()) {
|
||||
if (charset.name().equals(encoding.getJavaName())) {
|
||||
return encoding;
|
||||
}
|
||||
}
|
||||
}
|
||||
return JsonEncoding.UTF8;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* Provides an HttpMessageConverter implementations for handling JSON.
|
||||
*
|
||||
*/
|
||||
package org.springframework.http.converter.json;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* Provides an HttpMessageConverter abstraction to convert between Java objects and HTTP input/output messages.
|
||||
*
|
||||
*/
|
||||
package org.springframework.http.converter;
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter.xml;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Marshaller;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
|
||||
import org.springframework.http.converter.HttpMessageConversionException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverters} that
|
||||
* use JAXB2. Creates {@link JAXBContext} object lazily.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public abstract class AbstractJaxb2HttpMessageConverter<T> extends AbstractXmlHttpMessageConverter<T> {
|
||||
|
||||
private final ConcurrentMap<Class, JAXBContext> jaxbContexts = new ConcurrentHashMap<Class, JAXBContext>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link Marshaller} for the given class.
|
||||
*
|
||||
* @param clazz the class to create the marshaller for
|
||||
* @return the {@code Marshaller}
|
||||
* @throws HttpMessageConversionException in case of JAXB errors
|
||||
*/
|
||||
protected final Marshaller createMarshaller(Class clazz) {
|
||||
try {
|
||||
JAXBContext jaxbContext = getJaxbContext(clazz);
|
||||
return jaxbContext.createMarshaller();
|
||||
}
|
||||
catch (JAXBException ex) {
|
||||
throw new HttpMessageConversionException(
|
||||
"Could not create Marshaller for class [" + clazz + "]: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Unmarshaller} for the given class.
|
||||
*
|
||||
* @param clazz the class to create the unmarshaller for
|
||||
* @return the {@code Unmarshaller}
|
||||
* @throws HttpMessageConversionException in case of JAXB errors
|
||||
*/
|
||||
protected final Unmarshaller createUnmarshaller(Class clazz) throws JAXBException {
|
||||
try {
|
||||
JAXBContext jaxbContext = getJaxbContext(clazz);
|
||||
return jaxbContext.createUnmarshaller();
|
||||
}
|
||||
catch (JAXBException ex) {
|
||||
throw new HttpMessageConversionException(
|
||||
"Could not create Unmarshaller for class [" + clazz + "]: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link JAXBContext} for the given class.
|
||||
*
|
||||
* @param clazz the class to return the context for
|
||||
* @return the {@code JAXBContext}
|
||||
* @throws HttpMessageConversionException in case of JAXB errors
|
||||
*/
|
||||
protected final JAXBContext getJaxbContext(Class clazz) {
|
||||
Assert.notNull(clazz, "'clazz' must not be null");
|
||||
JAXBContext jaxbContext = jaxbContexts.get(clazz);
|
||||
if (jaxbContext == null) {
|
||||
try {
|
||||
jaxbContext = JAXBContext.newInstance(clazz);
|
||||
jaxbContexts.putIfAbsent(clazz, jaxbContext);
|
||||
}
|
||||
catch (JAXBException ex) {
|
||||
throw new HttpMessageConversionException(
|
||||
"Could not instantiate JAXBContext for class [" + clazz + "]: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
return jaxbContext;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter.xml;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConversionException;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverters}
|
||||
* that convert from/to XML.
|
||||
*
|
||||
* <p>By default, subclasses of this converter support {@code text/xml}, {@code application/xml}, and {@code
|
||||
* application/*-xml}. This can be overridden by setting the {@link #setSupportedMediaTypes(java.util.List)
|
||||
* supportedMediaTypes} property.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public abstract class AbstractXmlHttpMessageConverter<T> extends AbstractHttpMessageConverter<T> {
|
||||
|
||||
private final TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
|
||||
|
||||
/**
|
||||
* Protected constructor that sets the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes}
|
||||
* to {@code text/xml} and {@code application/xml}, and {@code application/*-xml}.
|
||||
*/
|
||||
protected AbstractXmlHttpMessageConverter() {
|
||||
super(MediaType.APPLICATION_XML, MediaType.TEXT_XML, new MediaType("application", "*+xml"));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException {
|
||||
return readFromSource(clazz, inputMessage.getHeaders(), new StreamSource(inputMessage.getBody()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void writeInternal(T t, HttpOutputMessage outputMessage) throws IOException {
|
||||
writeToResult(t, outputMessage.getHeaders(), new StreamResult(outputMessage.getBody()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the given {@code Source} to the {@code Result}.
|
||||
* @param source the source to transform from
|
||||
* @param result the result to transform to
|
||||
* @throws TransformerException in case of transformation errors
|
||||
*/
|
||||
protected void transform(Source source, Result result) throws TransformerException {
|
||||
this.transformerFactory.newTransformer().transform(source, result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Abstract template method called from {@link #read(Class, HttpInputMessage)}.
|
||||
* @param clazz the type of object to return
|
||||
* @param headers the HTTP input headers
|
||||
* @param source the HTTP input body
|
||||
* @return the converted object
|
||||
* @throws IOException in case of I/O errors
|
||||
* @throws org.springframework.http.converter.HttpMessageConversionException in case of conversion errors
|
||||
*/
|
||||
protected abstract T readFromSource(Class<? extends T> clazz, HttpHeaders headers, Source source)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Abstract template method called from {@link #writeInternal(Object, HttpOutputMessage)}.
|
||||
* @param t the object to write to the output message
|
||||
* @param headers the HTTP output headers
|
||||
* @param result the HTTP output body
|
||||
* @throws IOException in case of I/O errors
|
||||
* @throws HttpMessageConversionException in case of conversion errors
|
||||
*/
|
||||
protected abstract void writeToResult(T t, HttpHeaders headers, Result result)
|
||||
throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter.xml;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.xml.bind.JAXBElement;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.MarshalException;
|
||||
import javax.xml.bind.Marshaller;
|
||||
import javax.xml.bind.PropertyException;
|
||||
import javax.xml.bind.UnmarshalException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConversionException;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} that can read
|
||||
* and write XML using JAXB2.
|
||||
*
|
||||
* <p>This converter can read classes annotated with {@link XmlRootElement} and {@link XmlType}, and write classes
|
||||
* annotated with with {@link XmlRootElement}, or subclasses thereof.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessageConverter<Object> {
|
||||
|
||||
@Override
|
||||
public boolean canRead(Class<?> clazz, MediaType mediaType) {
|
||||
return (clazz.isAnnotationPresent(XmlRootElement.class) || clazz.isAnnotationPresent(XmlType.class)) &&
|
||||
canRead(mediaType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
|
||||
return AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) != null && canWrite(mediaType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
// should not be called, since we override canRead/Write
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source source) throws IOException {
|
||||
try {
|
||||
Unmarshaller unmarshaller = createUnmarshaller(clazz);
|
||||
if (clazz.isAnnotationPresent(XmlRootElement.class)) {
|
||||
return unmarshaller.unmarshal(source);
|
||||
}
|
||||
else {
|
||||
JAXBElement jaxbElement = unmarshaller.unmarshal(source, clazz);
|
||||
return jaxbElement.getValue();
|
||||
}
|
||||
}
|
||||
catch (UnmarshalException ex) {
|
||||
throw new HttpMessageNotReadableException("Could not unmarshal to [" + clazz + "]: " + ex.getMessage(), ex);
|
||||
|
||||
}
|
||||
catch (JAXBException ex) {
|
||||
throw new HttpMessageConversionException("Could not instantiate JAXBContext: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeToResult(Object o, HttpHeaders headers, Result result) throws IOException {
|
||||
try {
|
||||
Class clazz = ClassUtils.getUserClass(o);
|
||||
Marshaller marshaller = createMarshaller(clazz);
|
||||
setCharset(headers.getContentType(), marshaller);
|
||||
marshaller.marshal(o, result);
|
||||
}
|
||||
catch (MarshalException ex) {
|
||||
throw new HttpMessageNotWritableException("Could not marshal [" + o + "]: " + ex.getMessage(), ex);
|
||||
}
|
||||
catch (JAXBException ex) {
|
||||
throw new HttpMessageConversionException("Could not instantiate JAXBContext: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void setCharset(MediaType contentType, Marshaller marshaller) throws PropertyException {
|
||||
if (contentType != null && contentType.getCharSet() != null) {
|
||||
marshaller.setProperty(Marshaller.JAXB_ENCODING, contentType.getCharSet().name());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter.xml;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.oxm.Marshaller;
|
||||
import org.springframework.oxm.MarshallingFailureException;
|
||||
import org.springframework.oxm.Unmarshaller;
|
||||
import org.springframework.oxm.UnmarshallingFailureException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter}
|
||||
* that can read and write XML using Spring's {@link Marshaller} and {@link Unmarshaller} abstractions.
|
||||
*
|
||||
* <p>This converter requires a {@code Marshaller} and {@code Unmarshaller} before it can be used.
|
||||
* These can be injected by the {@linkplain #MarshallingHttpMessageConverter(Marshaller) constructor}
|
||||
* or {@linkplain #setMarshaller(Marshaller) bean properties}.
|
||||
*
|
||||
* <p>By default, this converter supports {@code text/xml} and {@code application/xml}. This can be
|
||||
* overridden by setting the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public class MarshallingHttpMessageConverter extends AbstractXmlHttpMessageConverter<Object> {
|
||||
|
||||
private Marshaller marshaller;
|
||||
|
||||
private Unmarshaller unmarshaller;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new {@code MarshallingHttpMessageConverter} with no {@link Marshaller} or
|
||||
* {@link Unmarshaller} set. The Marshaller and Unmarshaller must be set after construction
|
||||
* by invoking {@link #setMarshaller(Marshaller)} and {@link #setUnmarshaller(Unmarshaller)} .
|
||||
*/
|
||||
public MarshallingHttpMessageConverter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@code MarshallingMessageConverter} with the given {@link Marshaller} set.
|
||||
* <p>If the given {@link Marshaller} also implements the {@link Unmarshaller} interface,
|
||||
* it is used for both marshalling and unmarshalling. Otherwise, an exception is thrown.
|
||||
* <p>Note that all {@code Marshaller} implementations in Spring also implement the
|
||||
* {@code Unmarshaller} interface, so that you can safely use this constructor.
|
||||
* @param marshaller object used as marshaller and unmarshaller
|
||||
*/
|
||||
public MarshallingHttpMessageConverter(Marshaller marshaller) {
|
||||
Assert.notNull(marshaller, "Marshaller must not be null");
|
||||
this.marshaller = marshaller;
|
||||
if (marshaller instanceof Unmarshaller) {
|
||||
this.unmarshaller = (Unmarshaller) marshaller;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new <code>MarshallingMessageConverter</code> with the given
|
||||
* {@code Marshaller} and {@code Unmarshaller}.
|
||||
* @param marshaller the Marshaller to use
|
||||
* @param unmarshaller the Unmarshaller to use
|
||||
*/
|
||||
public MarshallingHttpMessageConverter(Marshaller marshaller, Unmarshaller unmarshaller) {
|
||||
Assert.notNull(marshaller, "Marshaller must not be null");
|
||||
Assert.notNull(unmarshaller, "Unmarshaller must not be null");
|
||||
this.marshaller = marshaller;
|
||||
this.unmarshaller = unmarshaller;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the {@link Marshaller} to be used by this message converter.
|
||||
*/
|
||||
public void setMarshaller(Marshaller marshaller) {
|
||||
this.marshaller = marshaller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link Unmarshaller} to be used by this message converter.
|
||||
*/
|
||||
public void setUnmarshaller(Unmarshaller unmarshaller) {
|
||||
this.unmarshaller = unmarshaller;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return this.unmarshaller.supports(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source source) throws IOException {
|
||||
Assert.notNull(this.unmarshaller, "Property 'unmarshaller' is required");
|
||||
try {
|
||||
Object result = this.unmarshaller.unmarshal(source);
|
||||
if (!clazz.isInstance(result)) {
|
||||
throw new TypeMismatchException(result, clazz);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (UnmarshallingFailureException ex) {
|
||||
throw new HttpMessageNotReadableException("Could not read [" + clazz + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeToResult(Object o, HttpHeaders headers, Result result) throws IOException {
|
||||
Assert.notNull(this.marshaller, "Property 'marshaller' is required");
|
||||
try {
|
||||
this.marshaller.marshal(o, result);
|
||||
}
|
||||
catch (MarshallingFailureException ex) {
|
||||
throw new HttpMessageNotWritableException("Could not write [" + o + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter.xml;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.dom.DOMResult;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.sax.SAXSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConversionException;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter} that can read and write {@link
|
||||
* Source} objects.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public class SourceHttpMessageConverter<T extends Source> extends AbstractXmlHttpMessageConverter<T> {
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return DOMSource.class.equals(clazz) || SAXSource.class.equals(clazz) || StreamSource.class.equals(clazz) ||
|
||||
Source.class.equals(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected T readFromSource(Class clazz, HttpHeaders headers, Source source) throws IOException {
|
||||
try {
|
||||
if (DOMSource.class.equals(clazz)) {
|
||||
DOMResult domResult = new DOMResult();
|
||||
transform(source, domResult);
|
||||
return (T) new DOMSource(domResult.getNode());
|
||||
}
|
||||
else if (SAXSource.class.equals(clazz)) {
|
||||
ByteArrayInputStream bis = transformToByteArrayInputStream(source);
|
||||
return (T) new SAXSource(new InputSource(bis));
|
||||
}
|
||||
else if (StreamSource.class.equals(clazz) || Source.class.equals(clazz)) {
|
||||
ByteArrayInputStream bis = transformToByteArrayInputStream(source);
|
||||
return (T) new StreamSource(bis);
|
||||
}
|
||||
else {
|
||||
throw new HttpMessageConversionException("Could not read class [" + clazz +
|
||||
"]. Only DOMSource, SAXSource, and StreamSource are supported.");
|
||||
}
|
||||
}
|
||||
catch (TransformerException ex) {
|
||||
throw new HttpMessageNotReadableException("Could not transform from [" + source + "] to [" + clazz + "]",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
private ByteArrayInputStream transformToByteArrayInputStream(Source source) throws TransformerException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
transform(source, new StreamResult(bos));
|
||||
return new ByteArrayInputStream(bos.toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Long getContentLength(T t, MediaType contentType) {
|
||||
if (t instanceof DOMSource) {
|
||||
try {
|
||||
CountingOutputStream os = new CountingOutputStream();
|
||||
transform(t, new StreamResult(os));
|
||||
return os.count;
|
||||
}
|
||||
catch (TransformerException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeToResult(T t, HttpHeaders headers, Result result) throws IOException {
|
||||
try {
|
||||
transform(t, result);
|
||||
}
|
||||
catch (TransformerException ex) {
|
||||
throw new HttpMessageNotWritableException("Could not transform [" + t + "] to [" + result + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CountingOutputStream extends OutputStream {
|
||||
|
||||
private long count = 0;
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
count++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b) throws IOException {
|
||||
count += b.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
count += len;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.converter.xml;
|
||||
|
||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||
|
||||
/**
|
||||
* Extension of {@link org.springframework.http.converter.FormHttpMessageConverter},
|
||||
* adding support for XML-based parts through a {@link SourceHttpMessageConverter}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0.3
|
||||
*/
|
||||
public class XmlAwareFormHttpMessageConverter extends FormHttpMessageConverter {
|
||||
|
||||
public XmlAwareFormHttpMessageConverter() {
|
||||
super();
|
||||
addPartConverter(new SourceHttpMessageConverter());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* Provides an HttpMessageConverter implementations for handling XML.
|
||||
*
|
||||
*/
|
||||
package org.springframework.http.converter.xml;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
/**
|
||||
*
|
||||
* Contains a basic abstraction over client/server-side HTTP. This package contains
|
||||
* the <code>HttpInputMessage</code> and <code>HttpOutputMessage</code> interfaces.
|
||||
*
|
||||
*/
|
||||
package org.springframework.http;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.server;
|
||||
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpRequest;
|
||||
|
||||
/**
|
||||
* Represents a server-side HTTP request.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface ServerHttpRequest extends HttpRequest, HttpInputMessage {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.server;
|
||||
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* Represents a server-side HTTP response.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface ServerHttpResponse extends HttpOutputMessage {
|
||||
|
||||
/**
|
||||
* Set the HTTP status code of the response.
|
||||
* @param status the HTTP status as an HttpStatus enum value
|
||||
*/
|
||||
void setStatusCode(HttpStatus status);
|
||||
|
||||
/**
|
||||
* Close this response, freeing any resources created.
|
||||
*/
|
||||
void close();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.server;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ServerHttpRequest} implementation that is based on a {@link HttpServletRequest}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
|
||||
protected static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";
|
||||
|
||||
protected static final String FORM_CHARSET = "UTF-8";
|
||||
|
||||
private static final String METHOD_POST = "POST";
|
||||
|
||||
private final HttpServletRequest servletRequest;
|
||||
|
||||
private HttpHeaders headers;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new instance of the ServletServerHttpRequest based on the given {@link HttpServletRequest}.
|
||||
* @param servletRequest the servlet request
|
||||
*/
|
||||
public ServletServerHttpRequest(HttpServletRequest servletRequest) {
|
||||
Assert.notNull(servletRequest, "'servletRequest' must not be null");
|
||||
this.servletRequest = servletRequest;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the {@code HttpServletRequest} this object is based on.
|
||||
*/
|
||||
public HttpServletRequest getServletRequest() {
|
||||
return this.servletRequest;
|
||||
}
|
||||
|
||||
public HttpMethod getMethod() {
|
||||
return HttpMethod.valueOf(this.servletRequest.getMethod());
|
||||
}
|
||||
|
||||
public URI getURI() {
|
||||
try {
|
||||
return new URI(this.servletRequest.getScheme(), null, this.servletRequest.getServerName(),
|
||||
this.servletRequest.getServerPort(), this.servletRequest.getRequestURI(),
|
||||
this.servletRequest.getQueryString(), null);
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
throw new IllegalStateException("Could not get HttpServletRequest URI: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
public HttpHeaders getHeaders() {
|
||||
if (this.headers == null) {
|
||||
this.headers = new HttpHeaders();
|
||||
for (Enumeration headerNames = this.servletRequest.getHeaderNames(); headerNames.hasMoreElements();) {
|
||||
String headerName = (String) headerNames.nextElement();
|
||||
for (Enumeration headerValues = this.servletRequest.getHeaders(headerName);
|
||||
headerValues.hasMoreElements();) {
|
||||
String headerValue = (String) headerValues.nextElement();
|
||||
this.headers.add(headerName, headerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
public InputStream getBody() throws IOException {
|
||||
if (isFormPost(this.servletRequest)) {
|
||||
return getBodyFromServletRequestParameters(this.servletRequest);
|
||||
}
|
||||
else {
|
||||
return this.servletRequest.getInputStream();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFormPost(HttpServletRequest request) {
|
||||
return (request.getContentType() != null && request.getContentType().contains(FORM_CONTENT_TYPE) &&
|
||||
METHOD_POST.equalsIgnoreCase(request.getMethod()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link javax.servlet.ServletRequest#getParameterMap()} to reconstruct the
|
||||
* body of a form 'POST' providing a predictable outcome as opposed to reading
|
||||
* from the body, which can fail if any other code has used ServletRequest
|
||||
* to access a parameter thus causing the input stream to be "consumed".
|
||||
*/
|
||||
private InputStream getBodyFromServletRequestParameters(HttpServletRequest request) throws IOException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
Writer writer = new OutputStreamWriter(bos, FORM_CHARSET);
|
||||
|
||||
Map<String, String[]> form = request.getParameterMap();
|
||||
for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext();) {
|
||||
String name = nameIterator.next();
|
||||
List<String> values = Arrays.asList(form.get(name));
|
||||
for (Iterator<String> valueIterator = values.iterator(); valueIterator.hasNext();) {
|
||||
String value = valueIterator.next();
|
||||
writer.write(URLEncoder.encode(name, FORM_CHARSET));
|
||||
if (value != null) {
|
||||
writer.write('=');
|
||||
writer.write(URLEncoder.encode(value, FORM_CHARSET));
|
||||
if (valueIterator.hasNext()) {
|
||||
writer.write('&');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nameIterator.hasNext()) {
|
||||
writer.append('&');
|
||||
}
|
||||
}
|
||||
writer.flush();
|
||||
|
||||
return new ByteArrayInputStream(bos.toByteArray());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.http.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ServerHttpResponse} implementation that is based on a {@link HttpServletResponse}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public class ServletServerHttpResponse implements ServerHttpResponse {
|
||||
|
||||
private final HttpServletResponse servletResponse;
|
||||
|
||||
private final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
private boolean headersWritten = false;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new instance of the ServletServerHttpResponse based on the given {@link HttpServletResponse}.
|
||||
* @param servletResponse the servlet response
|
||||
*/
|
||||
public ServletServerHttpResponse(HttpServletResponse servletResponse) {
|
||||
Assert.notNull(servletResponse, "'servletResponse' must not be null");
|
||||
this.servletResponse = servletResponse;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the {@code HttpServletResponse} this object is based on.
|
||||
*/
|
||||
public HttpServletResponse getServletResponse() {
|
||||
return this.servletResponse;
|
||||
}
|
||||
|
||||
public void setStatusCode(HttpStatus status) {
|
||||
this.servletResponse.setStatus(status.value());
|
||||
}
|
||||
|
||||
public HttpHeaders getHeaders() {
|
||||
return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
|
||||
}
|
||||
|
||||
public OutputStream getBody() throws IOException {
|
||||
writeHeaders();
|
||||
return this.servletResponse.getOutputStream();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
writeHeaders();
|
||||
}
|
||||
|
||||
private void writeHeaders() {
|
||||
if (!this.headersWritten) {
|
||||
for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
|
||||
String headerName = entry.getKey();
|
||||
for (String headerValue : entry.getValue()) {
|
||||
this.servletResponse.addHeader(headerName, headerValue);
|
||||
}
|
||||
}
|
||||
this.headersWritten = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
/**
|
||||
*
|
||||
* Contains an abstraction over server-side HTTP. This package
|
||||
* contains the <code>ServerHttpRequest</code> and
|
||||
* <code>ServerHttpResponse</code>, as well as a Servlet-based implementation of these
|
||||
* interfaces.
|
||||
*
|
||||
*/
|
||||
package org.springframework.http.server;
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.caucho;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
import com.caucho.burlap.client.BurlapProxyFactory;
|
||||
import com.caucho.burlap.client.BurlapRuntimeException;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.remoting.RemoteAccessException;
|
||||
import org.springframework.remoting.RemoteConnectFailureException;
|
||||
import org.springframework.remoting.RemoteLookupFailureException;
|
||||
import org.springframework.remoting.RemoteProxyFailureException;
|
||||
import org.springframework.remoting.support.UrlBasedRemoteAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link org.aopalliance.intercept.MethodInterceptor} for accessing a Burlap service.
|
||||
* Supports authentication via username and password.
|
||||
* The service URL must be an HTTP URL exposing a Burlap service.
|
||||
*
|
||||
* <p>Burlap is a slim, XML-based RPC protocol.
|
||||
* For information on Burlap, see the
|
||||
* <a href="http://www.caucho.com/burlap">Burlap website</a>
|
||||
*
|
||||
* <p>Note: There is no requirement for services accessed with this proxy factory
|
||||
* to have been exported using Spring's {@link BurlapServiceExporter}, as there is
|
||||
* no special handling involved. As a consequence, you can also access services that
|
||||
* have been exported using Caucho's {@link com.caucho.burlap.server.BurlapServlet}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 29.09.2003
|
||||
* @see #setServiceInterface
|
||||
* @see #setServiceUrl
|
||||
* @see #setUsername
|
||||
* @see #setPassword
|
||||
* @see BurlapServiceExporter
|
||||
* @see BurlapProxyFactoryBean
|
||||
* @see com.caucho.burlap.client.BurlapProxyFactory
|
||||
* @see com.caucho.burlap.server.BurlapServlet
|
||||
*/
|
||||
public class BurlapClientInterceptor extends UrlBasedRemoteAccessor implements MethodInterceptor {
|
||||
|
||||
private BurlapProxyFactory proxyFactory = new BurlapProxyFactory();
|
||||
|
||||
private Object burlapProxy;
|
||||
|
||||
|
||||
/**
|
||||
* Set the BurlapProxyFactory instance to use.
|
||||
* If not specified, a default BurlapProxyFactory will be created.
|
||||
* <p>Allows to use an externally configured factory instance,
|
||||
* in particular a custom BurlapProxyFactory subclass.
|
||||
*/
|
||||
public void setProxyFactory(BurlapProxyFactory proxyFactory) {
|
||||
this.proxyFactory = (proxyFactory != null ? proxyFactory : new BurlapProxyFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the username that this factory should use to access the remote service.
|
||||
* Default is none.
|
||||
* <p>The username will be sent by Burlap via HTTP Basic Authentication.
|
||||
* @see com.caucho.burlap.client.BurlapProxyFactory#setUser
|
||||
*/
|
||||
public void setUsername(String username) {
|
||||
this.proxyFactory.setUser(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the password that this factory should use to access the remote service.
|
||||
* Default is none.
|
||||
* <p>The password will be sent by Burlap via HTTP Basic Authentication.
|
||||
* @see com.caucho.burlap.client.BurlapProxyFactory#setPassword
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
this.proxyFactory.setPassword(password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether overloaded methods should be enabled for remote invocations.
|
||||
* Default is "false".
|
||||
* @see com.caucho.burlap.client.BurlapProxyFactory#setOverloadEnabled
|
||||
*/
|
||||
public void setOverloadEnabled(boolean overloadEnabled) {
|
||||
this.proxyFactory.setOverloadEnabled(overloadEnabled);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
prepare();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Burlap proxy for this interceptor.
|
||||
* @throws RemoteLookupFailureException if the service URL is invalid
|
||||
*/
|
||||
public void prepare() throws RemoteLookupFailureException {
|
||||
try {
|
||||
this.burlapProxy = createBurlapProxy(this.proxyFactory);
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new RemoteLookupFailureException("Service URL [" + getServiceUrl() + "] is invalid", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Burlap proxy that is wrapped by this interceptor.
|
||||
* @param proxyFactory the proxy factory to use
|
||||
* @return the Burlap proxy
|
||||
* @throws MalformedURLException if thrown by the proxy factory
|
||||
* @see com.caucho.burlap.client.BurlapProxyFactory#create
|
||||
*/
|
||||
protected Object createBurlapProxy(BurlapProxyFactory proxyFactory) throws MalformedURLException {
|
||||
Assert.notNull(getServiceInterface(), "Property 'serviceInterface' is required");
|
||||
return proxyFactory.create(getServiceInterface(), getServiceUrl());
|
||||
}
|
||||
|
||||
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
if (this.burlapProxy == null) {
|
||||
throw new IllegalStateException("BurlapClientInterceptor is not properly initialized - " +
|
||||
"invoke 'prepare' before attempting any operations");
|
||||
}
|
||||
|
||||
ClassLoader originalClassLoader = overrideThreadContextClassLoader();
|
||||
try {
|
||||
return invocation.getMethod().invoke(this.burlapProxy, invocation.getArguments());
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
Throwable targetEx = ex.getTargetException();
|
||||
if (targetEx instanceof BurlapRuntimeException) {
|
||||
Throwable cause = targetEx.getCause();
|
||||
throw convertBurlapAccessException(cause != null ? cause : targetEx);
|
||||
}
|
||||
else if (targetEx instanceof UndeclaredThrowableException) {
|
||||
UndeclaredThrowableException utex = (UndeclaredThrowableException) targetEx;
|
||||
throw convertBurlapAccessException(utex.getUndeclaredThrowable());
|
||||
}
|
||||
else {
|
||||
throw targetEx;
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new RemoteProxyFailureException(
|
||||
"Failed to invoke Burlap proxy for remote service [" + getServiceUrl() + "]", ex);
|
||||
}
|
||||
finally {
|
||||
resetThreadContextClassLoader(originalClassLoader);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given Burlap access exception to an appropriate
|
||||
* Spring RemoteAccessException.
|
||||
* @param ex the exception to convert
|
||||
* @return the RemoteAccessException to throw
|
||||
*/
|
||||
protected RemoteAccessException convertBurlapAccessException(Throwable ex) {
|
||||
if (ex instanceof ConnectException) {
|
||||
return new RemoteConnectFailureException(
|
||||
"Cannot connect to Burlap remote service at [" + getServiceUrl() + "]", ex);
|
||||
}
|
||||
else {
|
||||
return new RemoteAccessException(
|
||||
"Cannot access Burlap remote service at [" + getServiceUrl() + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.caucho;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.caucho.burlap.io.BurlapInput;
|
||||
import com.caucho.burlap.io.BurlapOutput;
|
||||
import com.caucho.burlap.server.BurlapSkeleton;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.remoting.support.RemoteExporter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* General stream-based protocol exporter for a Burlap endpoint.
|
||||
*
|
||||
* <p>Burlap is a slim, XML-based RPC protocol.
|
||||
* For information on Burlap, see the
|
||||
* <a href="http://www.caucho.com/burlap">Burlap website</a>.
|
||||
* This exporter requires Burlap 3.x.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5.1
|
||||
* @see #invoke(java.io.InputStream, java.io.OutputStream)
|
||||
* @see BurlapServiceExporter
|
||||
* @see SimpleBurlapServiceExporter
|
||||
*/
|
||||
public class BurlapExporter extends RemoteExporter implements InitializingBean {
|
||||
|
||||
private BurlapSkeleton skeleton;
|
||||
|
||||
|
||||
public void afterPropertiesSet() {
|
||||
prepare();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this service exporter.
|
||||
*/
|
||||
public void prepare() {
|
||||
checkService();
|
||||
checkServiceInterface();
|
||||
this.skeleton = new BurlapSkeleton(getProxyForService(), getServiceInterface());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform an invocation on the exported object.
|
||||
* @param inputStream the request stream
|
||||
* @param outputStream the response stream
|
||||
* @throws Throwable if invocation failed
|
||||
*/
|
||||
public void invoke(InputStream inputStream, OutputStream outputStream) throws Throwable {
|
||||
Assert.notNull(this.skeleton, "Burlap exporter has not been initialized");
|
||||
ClassLoader originalClassLoader = overrideThreadContextClassLoader();
|
||||
try {
|
||||
this.skeleton.invoke(new BurlapInput(inputStream), new BurlapOutput(outputStream));
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
inputStream.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
outputStream.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
resetThreadContextClassLoader(originalClassLoader);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.caucho;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
|
||||
/**
|
||||
* {@link FactoryBean} for Burlap proxies. Exposes the proxied service
|
||||
* for use as a bean reference, using the specified service interface.
|
||||
*
|
||||
* <p>Burlap is a slim, XML-based RPC protocol.
|
||||
* For information on Burlap, see the
|
||||
* <a href="http://www.caucho.com/burlap">Burlap website</a>
|
||||
*
|
||||
* <p>The service URL must be an HTTP URL exposing a Burlap service.
|
||||
* For details, see the {@link BurlapClientInterceptor} javadoc.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 13.05.2003
|
||||
* @see #setServiceInterface
|
||||
* @see #setServiceUrl
|
||||
* @see BurlapClientInterceptor
|
||||
* @see BurlapServiceExporter
|
||||
* @see org.springframework.remoting.caucho.HessianProxyFactoryBean
|
||||
* @see org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean
|
||||
* @see org.springframework.remoting.rmi.RmiProxyFactoryBean
|
||||
*/
|
||||
public class BurlapProxyFactoryBean extends BurlapClientInterceptor implements FactoryBean<Object> {
|
||||
|
||||
private Object serviceProxy;
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
|
||||
}
|
||||
|
||||
|
||||
public Object getObject() {
|
||||
return this.serviceProxy;
|
||||
}
|
||||
|
||||
public Class<?> getObjectType() {
|
||||
return getServiceInterface();
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.caucho;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.web.HttpRequestHandler;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.util.NestedServletException;
|
||||
|
||||
/**
|
||||
* Servlet-API-based HTTP request handler that exports the specified service bean
|
||||
* as Burlap service endpoint, accessible via a Burlap proxy.
|
||||
*
|
||||
* <p><b>Note:</b> Spring also provides an alternative version of this exporter,
|
||||
* for Sun's JRE 1.6 HTTP server: {@link SimpleBurlapServiceExporter}.
|
||||
*
|
||||
* <p>Burlap is a slim, XML-based RPC protocol.
|
||||
* For information on Burlap, see the
|
||||
* <a href="http://www.caucho.com/burlap">Burlap website</a>.
|
||||
* This exporter requires Burlap 3.x.
|
||||
*
|
||||
* <p>Note: Burlap services exported with this class can be accessed by
|
||||
* any Burlap client, as there isn't any special handling involved.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 13.05.2003
|
||||
* @see BurlapClientInterceptor
|
||||
* @see BurlapProxyFactoryBean
|
||||
* @see org.springframework.remoting.caucho.HessianServiceExporter
|
||||
* @see org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter
|
||||
* @see org.springframework.remoting.rmi.RmiServiceExporter
|
||||
*/
|
||||
public class BurlapServiceExporter extends BurlapExporter implements HttpRequestHandler {
|
||||
|
||||
/**
|
||||
* Processes the incoming Burlap request and creates a Burlap response.
|
||||
*/
|
||||
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
|
||||
if (!"POST".equals(request.getMethod())) {
|
||||
throw new HttpRequestMethodNotSupportedException(request.getMethod(),
|
||||
new String[] {"POST"}, "BurlapServiceExporter only supports POST requests");
|
||||
}
|
||||
|
||||
try {
|
||||
invoke(request.getInputStream(), response.getOutputStream());
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new NestedServletException("Burlap skeleton invocation failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.caucho;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
import com.caucho.hessian.HessianException;
|
||||
import com.caucho.hessian.client.HessianConnectionException;
|
||||
import com.caucho.hessian.client.HessianProxyFactory;
|
||||
import com.caucho.hessian.client.HessianRuntimeException;
|
||||
import com.caucho.hessian.io.SerializerFactory;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.remoting.RemoteAccessException;
|
||||
import org.springframework.remoting.RemoteConnectFailureException;
|
||||
import org.springframework.remoting.RemoteLookupFailureException;
|
||||
import org.springframework.remoting.RemoteProxyFailureException;
|
||||
import org.springframework.remoting.support.UrlBasedRemoteAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link org.aopalliance.intercept.MethodInterceptor} for accessing a Hessian service.
|
||||
* Supports authentication via username and password.
|
||||
* The service URL must be an HTTP URL exposing a Hessian service.
|
||||
*
|
||||
* <p>Hessian is a slim, binary RPC protocol.
|
||||
* For information on Hessian, see the
|
||||
* <a href="http://www.caucho.com/hessian">Hessian website</a>
|
||||
*
|
||||
* <p>Note: There is no requirement for services accessed with this proxy factory
|
||||
* to have been exported using Spring's {@link HessianServiceExporter}, as there is
|
||||
* no special handling involved. As a consequence, you can also access services that
|
||||
* have been exported using Caucho's {@link com.caucho.hessian.server.HessianServlet}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 29.09.2003
|
||||
* @see #setServiceInterface
|
||||
* @see #setServiceUrl
|
||||
* @see #setUsername
|
||||
* @see #setPassword
|
||||
* @see HessianServiceExporter
|
||||
* @see HessianProxyFactoryBean
|
||||
* @see com.caucho.hessian.client.HessianProxyFactory
|
||||
* @see com.caucho.hessian.server.HessianServlet
|
||||
*/
|
||||
public class HessianClientInterceptor extends UrlBasedRemoteAccessor implements MethodInterceptor {
|
||||
|
||||
private HessianProxyFactory proxyFactory = new HessianProxyFactory();
|
||||
|
||||
private Object hessianProxy;
|
||||
|
||||
|
||||
/**
|
||||
* Set the HessianProxyFactory instance to use.
|
||||
* If not specified, a default HessianProxyFactory will be created.
|
||||
* <p>Allows to use an externally configured factory instance,
|
||||
* in particular a custom HessianProxyFactory subclass.
|
||||
*/
|
||||
public void setProxyFactory(HessianProxyFactory proxyFactory) {
|
||||
this.proxyFactory = (proxyFactory != null ? proxyFactory : new HessianProxyFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the Hessian SerializerFactory to use.
|
||||
* <p>This will typically be passed in as an inner bean definition
|
||||
* of type <code>com.caucho.hessian.io.SerializerFactory</code>,
|
||||
* with custom bean property values applied.
|
||||
*/
|
||||
public void setSerializerFactory(SerializerFactory serializerFactory) {
|
||||
this.proxyFactory.setSerializerFactory(serializerFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to send the Java collection type for each serialized
|
||||
* collection. Default is "true".
|
||||
*/
|
||||
public void setSendCollectionType(boolean sendCollectionType) {
|
||||
this.proxyFactory.getSerializerFactory().setSendCollectionType(sendCollectionType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether overloaded methods should be enabled for remote invocations.
|
||||
* Default is "false".
|
||||
* @see com.caucho.hessian.client.HessianProxyFactory#setOverloadEnabled
|
||||
*/
|
||||
public void setOverloadEnabled(boolean overloadEnabled) {
|
||||
this.proxyFactory.setOverloadEnabled(overloadEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the username that this factory should use to access the remote service.
|
||||
* Default is none.
|
||||
* <p>The username will be sent by Hessian via HTTP Basic Authentication.
|
||||
* @see com.caucho.hessian.client.HessianProxyFactory#setUser
|
||||
*/
|
||||
public void setUsername(String username) {
|
||||
this.proxyFactory.setUser(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the password that this factory should use to access the remote service.
|
||||
* Default is none.
|
||||
* <p>The password will be sent by Hessian via HTTP Basic Authentication.
|
||||
* @see com.caucho.hessian.client.HessianProxyFactory#setPassword
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
this.proxyFactory.setPassword(password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether Hessian's debug mode should be enabled.
|
||||
* Default is "false".
|
||||
* @see com.caucho.hessian.client.HessianProxyFactory#setDebug
|
||||
*/
|
||||
public void setDebug(boolean debug) {
|
||||
this.proxyFactory.setDebug(debug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to use a chunked post for sending a Hessian request.
|
||||
* @see com.caucho.hessian.client.HessianProxyFactory#setChunkedPost
|
||||
*/
|
||||
public void setChunkedPost(boolean chunkedPost) {
|
||||
this.proxyFactory.setChunkedPost(chunkedPost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timeout to use when waiting for a reply from the Hessian service.
|
||||
* @see com.caucho.hessian.client.HessianProxyFactory#setReadTimeout
|
||||
*/
|
||||
public void setReadTimeout(long timeout) {
|
||||
this.proxyFactory.setReadTimeout(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether version 2 of the Hessian protocol should be used for
|
||||
* parsing requests and replies. Default is "false".
|
||||
* @see com.caucho.hessian.client.HessianProxyFactory#setHessian2Request
|
||||
*/
|
||||
public void setHessian2(boolean hessian2) {
|
||||
this.proxyFactory.setHessian2Request(hessian2);
|
||||
this.proxyFactory.setHessian2Reply(hessian2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether version 2 of the Hessian protocol should be used for
|
||||
* parsing requests. Default is "false".
|
||||
* @see com.caucho.hessian.client.HessianProxyFactory#setHessian2Request
|
||||
*/
|
||||
public void setHessian2Request(boolean hessian2) {
|
||||
this.proxyFactory.setHessian2Request(hessian2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether version 2 of the Hessian protocol should be used for
|
||||
* parsing replies. Default is "false".
|
||||
* @see com.caucho.hessian.client.HessianProxyFactory#setHessian2Reply
|
||||
*/
|
||||
public void setHessian2Reply(boolean hessian2) {
|
||||
this.proxyFactory.setHessian2Reply(hessian2);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
prepare();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Hessian proxy for this interceptor.
|
||||
* @throws RemoteLookupFailureException if the service URL is invalid
|
||||
*/
|
||||
public void prepare() throws RemoteLookupFailureException {
|
||||
try {
|
||||
this.hessianProxy = createHessianProxy(this.proxyFactory);
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new RemoteLookupFailureException("Service URL [" + getServiceUrl() + "] is invalid", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Hessian proxy that is wrapped by this interceptor.
|
||||
* @param proxyFactory the proxy factory to use
|
||||
* @return the Hessian proxy
|
||||
* @throws MalformedURLException if thrown by the proxy factory
|
||||
* @see com.caucho.hessian.client.HessianProxyFactory#create
|
||||
*/
|
||||
protected Object createHessianProxy(HessianProxyFactory proxyFactory) throws MalformedURLException {
|
||||
Assert.notNull(getServiceInterface(), "'serviceInterface' is required");
|
||||
return proxyFactory.create(getServiceInterface(), getServiceUrl());
|
||||
}
|
||||
|
||||
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
if (this.hessianProxy == null) {
|
||||
throw new IllegalStateException("HessianClientInterceptor is not properly initialized - " +
|
||||
"invoke 'prepare' before attempting any operations");
|
||||
}
|
||||
|
||||
ClassLoader originalClassLoader = overrideThreadContextClassLoader();
|
||||
try {
|
||||
return invocation.getMethod().invoke(this.hessianProxy, invocation.getArguments());
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
Throwable targetEx = ex.getTargetException();
|
||||
// Hessian 4.0 check: another layer of InvocationTargetException.
|
||||
if (targetEx instanceof InvocationTargetException) {
|
||||
targetEx = ((InvocationTargetException) targetEx).getTargetException();
|
||||
}
|
||||
if (targetEx instanceof HessianConnectionException) {
|
||||
throw convertHessianAccessException(targetEx);
|
||||
}
|
||||
else if (targetEx instanceof HessianException || targetEx instanceof HessianRuntimeException) {
|
||||
Throwable cause = targetEx.getCause();
|
||||
throw convertHessianAccessException(cause != null ? cause : targetEx);
|
||||
}
|
||||
else if (targetEx instanceof UndeclaredThrowableException) {
|
||||
UndeclaredThrowableException utex = (UndeclaredThrowableException) targetEx;
|
||||
throw convertHessianAccessException(utex.getUndeclaredThrowable());
|
||||
}
|
||||
else {
|
||||
throw targetEx;
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new RemoteProxyFailureException(
|
||||
"Failed to invoke Hessian proxy for remote service [" + getServiceUrl() + "]", ex);
|
||||
}
|
||||
finally {
|
||||
resetThreadContextClassLoader(originalClassLoader);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given Hessian access exception to an appropriate
|
||||
* Spring RemoteAccessException.
|
||||
* @param ex the exception to convert
|
||||
* @return the RemoteAccessException to throw
|
||||
*/
|
||||
protected RemoteAccessException convertHessianAccessException(Throwable ex) {
|
||||
if (ex instanceof HessianConnectionException || ex instanceof ConnectException) {
|
||||
return new RemoteConnectFailureException(
|
||||
"Cannot connect to Hessian remote service at [" + getServiceUrl() + "]", ex);
|
||||
}
|
||||
else {
|
||||
return new RemoteAccessException(
|
||||
"Cannot access Hessian remote service at [" + getServiceUrl() + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.caucho;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import com.caucho.hessian.io.AbstractHessianInput;
|
||||
import com.caucho.hessian.io.AbstractHessianOutput;
|
||||
import com.caucho.hessian.io.Hessian2Input;
|
||||
import com.caucho.hessian.io.Hessian2Output;
|
||||
import com.caucho.hessian.io.HessianDebugInputStream;
|
||||
import com.caucho.hessian.io.HessianDebugOutputStream;
|
||||
import com.caucho.hessian.io.HessianInput;
|
||||
import com.caucho.hessian.io.HessianOutput;
|
||||
import com.caucho.hessian.io.SerializerFactory;
|
||||
import com.caucho.hessian.server.HessianSkeleton;
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.remoting.support.RemoteExporter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CommonsLogWriter;
|
||||
|
||||
/**
|
||||
* General stream-based protocol exporter for a Hessian endpoint.
|
||||
*
|
||||
* <p>Hessian is a slim, binary RPC protocol.
|
||||
* For information on Hessian, see the
|
||||
* <a href="http://www.caucho.com/hessian">Hessian website</a>.
|
||||
* <b>Note: As of Spring 3.0, this exporter requires Hessian 3.2 or above.</b>
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5.1
|
||||
* @see #invoke(java.io.InputStream, java.io.OutputStream)
|
||||
* @see HessianServiceExporter
|
||||
* @see SimpleHessianServiceExporter
|
||||
*/
|
||||
public class HessianExporter extends RemoteExporter implements InitializingBean {
|
||||
|
||||
public static final String CONTENT_TYPE_HESSIAN = "application/x-hessian";
|
||||
|
||||
|
||||
private SerializerFactory serializerFactory = new SerializerFactory();
|
||||
|
||||
private Log debugLogger;
|
||||
|
||||
private HessianSkeleton skeleton;
|
||||
|
||||
|
||||
/**
|
||||
* Specify the Hessian SerializerFactory to use.
|
||||
* <p>This will typically be passed in as an inner bean definition
|
||||
* of type <code>com.caucho.hessian.io.SerializerFactory</code>,
|
||||
* with custom bean property values applied.
|
||||
*/
|
||||
public void setSerializerFactory(SerializerFactory serializerFactory) {
|
||||
this.serializerFactory = (serializerFactory != null ? serializerFactory : new SerializerFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to send the Java collection type for each serialized
|
||||
* collection. Default is "true".
|
||||
*/
|
||||
public void setSendCollectionType(boolean sendCollectionType) {
|
||||
this.serializerFactory.setSendCollectionType(sendCollectionType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether Hessian's debug mode should be enabled, logging to
|
||||
* this exporter's Commons Logging log. Default is "false".
|
||||
* @see com.caucho.hessian.client.HessianProxyFactory#setDebug
|
||||
*/
|
||||
public void setDebug(boolean debug) {
|
||||
this.debugLogger = (debug ? logger : null);
|
||||
}
|
||||
|
||||
|
||||
public void afterPropertiesSet() {
|
||||
prepare();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this exporter.
|
||||
*/
|
||||
public void prepare() {
|
||||
checkService();
|
||||
checkServiceInterface();
|
||||
this.skeleton = new HessianSkeleton(getProxyForService(), getServiceInterface());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform an invocation on the exported object.
|
||||
* @param inputStream the request stream
|
||||
* @param outputStream the response stream
|
||||
* @throws Throwable if invocation failed
|
||||
*/
|
||||
public void invoke(InputStream inputStream, OutputStream outputStream) throws Throwable {
|
||||
Assert.notNull(this.skeleton, "Hessian exporter has not been initialized");
|
||||
doInvoke(this.skeleton, inputStream, outputStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually invoke the skeleton with the given streams.
|
||||
* @param skeleton the skeleton to invoke
|
||||
* @param inputStream the request stream
|
||||
* @param outputStream the response stream
|
||||
* @throws Throwable if invocation failed
|
||||
*/
|
||||
protected void doInvoke(HessianSkeleton skeleton, InputStream inputStream, OutputStream outputStream)
|
||||
throws Throwable {
|
||||
|
||||
ClassLoader originalClassLoader = overrideThreadContextClassLoader();
|
||||
try {
|
||||
InputStream isToUse = inputStream;
|
||||
OutputStream osToUse = outputStream;
|
||||
|
||||
if (this.debugLogger != null && this.debugLogger.isDebugEnabled()) {
|
||||
PrintWriter debugWriter = new PrintWriter(new CommonsLogWriter(this.debugLogger));
|
||||
HessianDebugInputStream dis = new HessianDebugInputStream(inputStream, debugWriter);
|
||||
dis.startTop2();
|
||||
HessianDebugOutputStream dos = new HessianDebugOutputStream(outputStream, debugWriter);
|
||||
dos.startTop2();
|
||||
isToUse = dis;
|
||||
osToUse = dos;
|
||||
}
|
||||
|
||||
if (!isToUse.markSupported()) {
|
||||
isToUse = new BufferedInputStream(isToUse);
|
||||
isToUse.mark(1);
|
||||
}
|
||||
|
||||
int code = isToUse.read();
|
||||
int major;
|
||||
int minor;
|
||||
|
||||
AbstractHessianInput in;
|
||||
AbstractHessianOutput out;
|
||||
|
||||
if (code == 'H') {
|
||||
// Hessian 2.0 stream
|
||||
major = isToUse.read();
|
||||
minor = isToUse.read();
|
||||
if (major != 0x02) {
|
||||
throw new IOException("Version " + major + "." + minor + " is not understood");
|
||||
}
|
||||
in = new Hessian2Input(isToUse);
|
||||
out = new Hessian2Output(osToUse);
|
||||
in.readCall();
|
||||
}
|
||||
else if (code == 'C') {
|
||||
// Hessian 2.0 call... for some reason not handled in HessianServlet!
|
||||
isToUse.reset();
|
||||
in = new Hessian2Input(isToUse);
|
||||
out = new Hessian2Output(osToUse);
|
||||
in.readCall();
|
||||
}
|
||||
else if (code == 'c') {
|
||||
// Hessian 1.0 call
|
||||
major = isToUse.read();
|
||||
minor = isToUse.read();
|
||||
in = new HessianInput(isToUse);
|
||||
if (major >= 2) {
|
||||
out = new Hessian2Output(osToUse);
|
||||
}
|
||||
else {
|
||||
out = new HessianOutput(osToUse);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IOException("Expected 'H'/'C' (Hessian 2.0) or 'c' (Hessian 1.0) in hessian input at " + code);
|
||||
}
|
||||
|
||||
if (this.serializerFactory != null) {
|
||||
in.setSerializerFactory(this.serializerFactory);
|
||||
out.setSerializerFactory(this.serializerFactory);
|
||||
}
|
||||
|
||||
try {
|
||||
skeleton.invoke(in, out);
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
in.close();
|
||||
isToUse.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
out.close();
|
||||
osToUse.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
resetThreadContextClassLoader(originalClassLoader);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.caucho;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
|
||||
/**
|
||||
* {@link FactoryBean} for Hessian proxies. Exposes the proxied service
|
||||
* for use as a bean reference, using the specified service interface.
|
||||
*
|
||||
* <p>Hessian is a slim, binary RPC protocol.
|
||||
* For information on Hessian, see the
|
||||
* <a href="http://www.caucho.com/hessian">Hessian website</a>
|
||||
*
|
||||
* <p>The service URL must be an HTTP URL exposing a Hessian service.
|
||||
* For details, see the {@link HessianClientInterceptor} javadoc.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 13.05.2003
|
||||
* @see #setServiceInterface
|
||||
* @see #setServiceUrl
|
||||
* @see HessianClientInterceptor
|
||||
* @see HessianServiceExporter
|
||||
* @see org.springframework.remoting.caucho.BurlapProxyFactoryBean
|
||||
* @see org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean
|
||||
* @see org.springframework.remoting.rmi.RmiProxyFactoryBean
|
||||
*/
|
||||
public class HessianProxyFactoryBean extends HessianClientInterceptor implements FactoryBean<Object> {
|
||||
|
||||
private Object serviceProxy;
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
|
||||
}
|
||||
|
||||
|
||||
public Object getObject() {
|
||||
return this.serviceProxy;
|
||||
}
|
||||
|
||||
public Class<?> getObjectType() {
|
||||
return getServiceInterface();
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.caucho;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.web.HttpRequestHandler;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.util.NestedServletException;
|
||||
|
||||
/**
|
||||
* Servlet-API-based HTTP request handler that exports the specified service bean
|
||||
* as Hessian service endpoint, accessible via a Hessian proxy.
|
||||
*
|
||||
* <p><b>Note:</b> Spring also provides an alternative version of this exporter,
|
||||
* for Sun's JRE 1.6 HTTP server: {@link SimpleHessianServiceExporter}.
|
||||
*
|
||||
* <p>Hessian is a slim, binary RPC protocol.
|
||||
* For information on Hessian, see the
|
||||
* <a href="http://www.caucho.com/hessian">Hessian website</a>.
|
||||
* <b>Note: As of Spring 3.0, this exporter requires Hessian 3.2 or above.</b>
|
||||
*
|
||||
* <p>Hessian services exported with this class can be accessed by
|
||||
* any Hessian client, as there isn't any special handling involved.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 13.05.2003
|
||||
* @see HessianClientInterceptor
|
||||
* @see HessianProxyFactoryBean
|
||||
* @see org.springframework.remoting.caucho.BurlapServiceExporter
|
||||
* @see org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter
|
||||
* @see org.springframework.remoting.rmi.RmiServiceExporter
|
||||
*/
|
||||
public class HessianServiceExporter extends HessianExporter implements HttpRequestHandler {
|
||||
|
||||
/**
|
||||
* Processes the incoming Hessian request and creates a Hessian response.
|
||||
*/
|
||||
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
|
||||
if (!"POST".equals(request.getMethod())) {
|
||||
throw new HttpRequestMethodNotSupportedException(request.getMethod(),
|
||||
new String[] {"POST"}, "HessianServiceExporter only supports POST requests");
|
||||
}
|
||||
|
||||
response.setContentType(CONTENT_TYPE_HESSIAN);
|
||||
try {
|
||||
invoke(request.getInputStream(), response.getOutputStream());
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new NestedServletException("Hessian skeleton invocation failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.caucho;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* HTTP request handler that exports the specified service bean as
|
||||
* Burlap service endpoint, accessible via a Burlap proxy.
|
||||
* Designed for Sun's JRE 1.6 HTTP server, implementing the
|
||||
* {@link com.sun.net.httpserver.HttpHandler} interface.
|
||||
*
|
||||
* <p>Burlap is a slim, XML-based RPC protocol.
|
||||
* For information on Burlap, see the
|
||||
* <a href="http://www.caucho.com/burlap">Burlap website</a>.
|
||||
* This exporter requires Burlap 3.x.
|
||||
*
|
||||
* <p>Note: Burlap services exported with this class can be accessed by
|
||||
* any Burlap client, as there isn't any special handling involved.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5.1
|
||||
* @see org.springframework.remoting.caucho.BurlapClientInterceptor
|
||||
* @see org.springframework.remoting.caucho.BurlapProxyFactoryBean
|
||||
* @see SimpleHessianServiceExporter
|
||||
* @see org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter
|
||||
*/
|
||||
public class SimpleBurlapServiceExporter extends BurlapExporter implements HttpHandler {
|
||||
|
||||
/**
|
||||
* Processes the incoming Burlap request and creates a Burlap response.
|
||||
*/
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
if (!"POST".equals(exchange.getRequestMethod())) {
|
||||
exchange.getResponseHeaders().set("Allow", "POST");
|
||||
exchange.sendResponseHeaders(405, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream(1024);
|
||||
try {
|
||||
invoke(exchange.getRequestBody(), output);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
exchange.sendResponseHeaders(500, -1);
|
||||
logger.error("Burlap skeleton invocation failed", ex);
|
||||
}
|
||||
|
||||
exchange.sendResponseHeaders(200, output.size());
|
||||
FileCopyUtils.copy(output.toByteArray(), exchange.getResponseBody());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.caucho;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* HTTP request handler that exports the specified service bean as
|
||||
* Hessian service endpoint, accessible via a Hessian proxy.
|
||||
* Designed for Sun's JRE 1.6 HTTP server, implementing the
|
||||
* {@link com.sun.net.httpserver.HttpHandler} interface.
|
||||
*
|
||||
* <p>Hessian is a slim, binary RPC protocol.
|
||||
* For information on Hessian, see the
|
||||
* <a href="http://www.caucho.com/hessian">Hessian website</a>.
|
||||
* <b>Note: As of Spring 3.0, this exporter requires Hessian 3.2 or above.</b>
|
||||
*
|
||||
* <p>Hessian services exported with this class can be accessed by
|
||||
* any Hessian client, as there isn't any special handling involved.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5.1
|
||||
* @see org.springframework.remoting.caucho.HessianClientInterceptor
|
||||
* @see org.springframework.remoting.caucho.HessianProxyFactoryBean
|
||||
* @see SimpleBurlapServiceExporter
|
||||
* @see org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter
|
||||
*/
|
||||
public class SimpleHessianServiceExporter extends HessianExporter implements HttpHandler {
|
||||
|
||||
/**
|
||||
* Processes the incoming Hessian request and creates a Hessian response.
|
||||
*/
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
if (!"POST".equals(exchange.getRequestMethod())) {
|
||||
exchange.getResponseHeaders().set("Allow", "POST");
|
||||
exchange.sendResponseHeaders(405, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream(1024);
|
||||
try {
|
||||
invoke(exchange.getRequestBody(), output);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
exchange.sendResponseHeaders(500, -1);
|
||||
logger.error("Hessian skeleton invocation failed", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
exchange.getResponseHeaders().set("Content-Type", CONTENT_TYPE_HESSIAN);
|
||||
exchange.sendResponseHeaders(200, output.size());
|
||||
FileCopyUtils.copy(output.toByteArray(), exchange.getResponseBody());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
/**
|
||||
*
|
||||
* This package provides remoting classes for Caucho's Hessian and Burlap
|
||||
* protocols: a proxy factory for accessing Hessian/Burlap services,
|
||||
* and an exporter for making beans available to Hessian/Burlap clients.
|
||||
*
|
||||
* <p>Hessian is a slim, binary RPC protocol over HTTP.
|
||||
* For information on Hessian, see the
|
||||
* <a href="http://www.caucho.com/hessian">Hessian website</a>
|
||||
*
|
||||
* <p>Burlap is a slim, XML-based RPC protocol over HTTP.
|
||||
* For information on Burlap, see the
|
||||
* <a href="http://www.caucho.com/burlap">Burlap website</a>
|
||||
*
|
||||
*/
|
||||
package org.springframework.remoting.caucho;
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.httpinvoker;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.rmi.RemoteException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.remoting.rmi.CodebaseAwareObjectInputStream;
|
||||
import org.springframework.remoting.support.RemoteInvocation;
|
||||
import org.springframework.remoting.support.RemoteInvocationResult;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract base implementation of the HttpInvokerRequestExecutor interface.
|
||||
*
|
||||
* <p>Pre-implements serialization of RemoteInvocation objects and
|
||||
* deserialization of RemoteInvocationResults objects.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
* @see #doExecuteRequest
|
||||
*/
|
||||
public abstract class AbstractHttpInvokerRequestExecutor
|
||||
implements HttpInvokerRequestExecutor, BeanClassLoaderAware {
|
||||
|
||||
/**
|
||||
* Default content type: "application/x-java-serialized-object"
|
||||
*/
|
||||
public static final String CONTENT_TYPE_SERIALIZED_OBJECT = "application/x-java-serialized-object";
|
||||
|
||||
|
||||
protected static final String HTTP_METHOD_POST = "POST";
|
||||
|
||||
protected static final String HTTP_HEADER_ACCEPT_LANGUAGE = "Accept-Language";
|
||||
|
||||
protected static final String HTTP_HEADER_ACCEPT_ENCODING = "Accept-Encoding";
|
||||
|
||||
protected static final String HTTP_HEADER_CONTENT_ENCODING = "Content-Encoding";
|
||||
|
||||
protected static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type";
|
||||
|
||||
protected static final String HTTP_HEADER_CONTENT_LENGTH = "Content-Length";
|
||||
|
||||
protected static final String ENCODING_GZIP = "gzip";
|
||||
|
||||
|
||||
private static final int SERIALIZED_INVOCATION_BYTE_ARRAY_INITIAL_SIZE = 1024;
|
||||
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private String contentType = CONTENT_TYPE_SERIALIZED_OBJECT;
|
||||
|
||||
private boolean acceptGzipEncoding = true;
|
||||
|
||||
private ClassLoader beanClassLoader;
|
||||
|
||||
|
||||
/**
|
||||
* Specify the content type to use for sending HTTP invoker requests.
|
||||
* <p>Default is "application/x-java-serialized-object".
|
||||
*/
|
||||
public void setContentType(String contentType) {
|
||||
Assert.notNull(contentType, "'contentType' must not be null");
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the content type to use for sending HTTP invoker requests.
|
||||
*/
|
||||
public String getContentType() {
|
||||
return this.contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to accept GZIP encoding, that is, whether to
|
||||
* send the HTTP "Accept-Encoding" header with "gzip" as value.
|
||||
* <p>Default is "true". Turn this flag off if you do not want
|
||||
* GZIP response compression even if enabled on the HTTP server.
|
||||
*/
|
||||
public void setAcceptGzipEncoding(boolean acceptGzipEncoding) {
|
||||
this.acceptGzipEncoding = acceptGzipEncoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to accept GZIP encoding, that is, whether to
|
||||
* send the HTTP "Accept-Encoding" header with "gzip" as value.
|
||||
*/
|
||||
public boolean isAcceptGzipEncoding() {
|
||||
return this.acceptGzipEncoding;
|
||||
}
|
||||
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.beanClassLoader = classLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean ClassLoader that this executor is supposed to use.
|
||||
*/
|
||||
protected ClassLoader getBeanClassLoader() {
|
||||
return this.beanClassLoader;
|
||||
}
|
||||
|
||||
|
||||
public final RemoteInvocationResult executeRequest(
|
||||
HttpInvokerClientConfiguration config, RemoteInvocation invocation) throws Exception {
|
||||
|
||||
ByteArrayOutputStream baos = getByteArrayOutputStream(invocation);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Sending HTTP invoker request for service at [" + config.getServiceUrl() +
|
||||
"], with size " + baos.size());
|
||||
}
|
||||
return doExecuteRequest(config, baos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the given RemoteInvocation into a ByteArrayOutputStream.
|
||||
* @param invocation the RemoteInvocation object
|
||||
* @return a ByteArrayOutputStream with the serialized RemoteInvocation
|
||||
* @throws IOException if thrown by I/O methods
|
||||
*/
|
||||
protected ByteArrayOutputStream getByteArrayOutputStream(RemoteInvocation invocation) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(SERIALIZED_INVOCATION_BYTE_ARRAY_INITIAL_SIZE);
|
||||
writeRemoteInvocation(invocation, baos);
|
||||
return baos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the given RemoteInvocation to the given OutputStream.
|
||||
* <p>The default implementation gives <code>decorateOutputStream</code> a chance
|
||||
* to decorate the stream first (for example, for custom encryption or compression).
|
||||
* Creates an <code>ObjectOutputStream</code> for the final stream and calls
|
||||
* <code>doWriteRemoteInvocation</code> to actually write the object.
|
||||
* <p>Can be overridden for custom serialization of the invocation.
|
||||
* @param invocation the RemoteInvocation object
|
||||
* @param os the OutputStream to write to
|
||||
* @throws IOException if thrown by I/O methods
|
||||
* @see #decorateOutputStream
|
||||
* @see #doWriteRemoteInvocation
|
||||
*/
|
||||
protected void writeRemoteInvocation(RemoteInvocation invocation, OutputStream os) throws IOException {
|
||||
ObjectOutputStream oos = new ObjectOutputStream(decorateOutputStream(os));
|
||||
try {
|
||||
doWriteRemoteInvocation(invocation, oos);
|
||||
}
|
||||
finally {
|
||||
oos.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the OutputStream to use for writing remote invocations,
|
||||
* potentially decorating the given original OutputStream.
|
||||
* <p>The default implementation returns the given stream as-is.
|
||||
* Can be overridden, for example, for custom encryption or compression.
|
||||
* @param os the original OutputStream
|
||||
* @return the potentially decorated OutputStream
|
||||
*/
|
||||
protected OutputStream decorateOutputStream(OutputStream os) throws IOException {
|
||||
return os;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual writing of the given invocation object to the
|
||||
* given ObjectOutputStream.
|
||||
* <p>The default implementation simply calls <code>writeObject</code>.
|
||||
* Can be overridden for serialization of a custom wrapper object rather
|
||||
* than the plain invocation, for example an encryption-aware holder.
|
||||
* @param invocation the RemoteInvocation object
|
||||
* @param oos the ObjectOutputStream to write to
|
||||
* @throws IOException if thrown by I/O methods
|
||||
* @see java.io.ObjectOutputStream#writeObject
|
||||
*/
|
||||
protected void doWriteRemoteInvocation(RemoteInvocation invocation, ObjectOutputStream oos) throws IOException {
|
||||
oos.writeObject(invocation);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute a request to send the given serialized remote invocation.
|
||||
* <p>Implementations will usually call <code>readRemoteInvocationResult</code>
|
||||
* to deserialize a returned RemoteInvocationResult object.
|
||||
* @param config the HTTP invoker configuration that specifies the
|
||||
* target service
|
||||
* @param baos the ByteArrayOutputStream that contains the serialized
|
||||
* RemoteInvocation object
|
||||
* @return the RemoteInvocationResult object
|
||||
* @throws IOException if thrown by I/O operations
|
||||
* @throws ClassNotFoundException if thrown during deserialization
|
||||
* @throws Exception in case of general errors
|
||||
* @see #readRemoteInvocationResult(java.io.InputStream, String)
|
||||
*/
|
||||
protected abstract RemoteInvocationResult doExecuteRequest(
|
||||
HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
|
||||
throws Exception;
|
||||
|
||||
/**
|
||||
* Deserialize a RemoteInvocationResult object from the given InputStream.
|
||||
* <p>Gives <code>decorateInputStream</code> a chance to decorate the stream
|
||||
* first (for example, for custom encryption or compression). Creates an
|
||||
* <code>ObjectInputStream</code> via <code>createObjectInputStream</code> and
|
||||
* calls <code>doReadRemoteInvocationResult</code> to actually read the object.
|
||||
* <p>Can be overridden for custom serialization of the invocation.
|
||||
* @param is the InputStream to read from
|
||||
* @param codebaseUrl the codebase URL to load classes from if not found locally
|
||||
* @return the RemoteInvocationResult object
|
||||
* @throws IOException if thrown by I/O methods
|
||||
* @throws ClassNotFoundException if thrown during deserialization
|
||||
* @see #decorateInputStream
|
||||
* @see #createObjectInputStream
|
||||
* @see #doReadRemoteInvocationResult
|
||||
*/
|
||||
protected RemoteInvocationResult readRemoteInvocationResult(InputStream is, String codebaseUrl)
|
||||
throws IOException, ClassNotFoundException {
|
||||
|
||||
ObjectInputStream ois = createObjectInputStream(decorateInputStream(is), codebaseUrl);
|
||||
try {
|
||||
return doReadRemoteInvocationResult(ois);
|
||||
}
|
||||
finally {
|
||||
ois.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the InputStream to use for reading remote invocation results,
|
||||
* potentially decorating the given original InputStream.
|
||||
* <p>The default implementation returns the given stream as-is.
|
||||
* Can be overridden, for example, for custom encryption or compression.
|
||||
* @param is the original InputStream
|
||||
* @return the potentially decorated InputStream
|
||||
*/
|
||||
protected InputStream decorateInputStream(InputStream is) throws IOException {
|
||||
return is;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ObjectInputStream for the given InputStream and codebase.
|
||||
* The default implementation creates a CodebaseAwareObjectInputStream.
|
||||
* @param is the InputStream to read from
|
||||
* @param codebaseUrl the codebase URL to load classes from if not found locally
|
||||
* (can be <code>null</code>)
|
||||
* @return the new ObjectInputStream instance to use
|
||||
* @throws IOException if creation of the ObjectInputStream failed
|
||||
* @see org.springframework.remoting.rmi.CodebaseAwareObjectInputStream
|
||||
*/
|
||||
protected ObjectInputStream createObjectInputStream(InputStream is, String codebaseUrl) throws IOException {
|
||||
return new CodebaseAwareObjectInputStream(is, getBeanClassLoader(), codebaseUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual reading of an invocation object from the
|
||||
* given ObjectInputStream.
|
||||
* <p>The default implementation simply calls <code>readObject</code>.
|
||||
* Can be overridden for deserialization of a custom wrapper object rather
|
||||
* than the plain invocation, for example an encryption-aware holder.
|
||||
* @param ois the ObjectInputStream to read from
|
||||
* @return the RemoteInvocationResult object
|
||||
* @throws IOException if thrown by I/O methods
|
||||
* @throws ClassNotFoundException if the class name of a serialized object
|
||||
* couldn't get resolved
|
||||
* @see java.io.ObjectOutputStream#writeObject
|
||||
*/
|
||||
protected RemoteInvocationResult doReadRemoteInvocationResult(ObjectInputStream ois)
|
||||
throws IOException, ClassNotFoundException {
|
||||
|
||||
Object obj = ois.readObject();
|
||||
if (!(obj instanceof RemoteInvocationResult)) {
|
||||
throw new RemoteException("Deserialized object needs to be assignable to type [" +
|
||||
RemoteInvocationResult.class.getName() + "]: " + obj);
|
||||
}
|
||||
return (RemoteInvocationResult) obj;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.httpinvoker;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import org.apache.commons.httpclient.Header;
|
||||
import org.apache.commons.httpclient.HttpClient;
|
||||
import org.apache.commons.httpclient.HttpException;
|
||||
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
|
||||
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
|
||||
import org.apache.commons.httpclient.methods.PostMethod;
|
||||
|
||||
import org.springframework.context.i18n.LocaleContext;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.remoting.support.RemoteInvocationResult;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link HttpInvokerRequestExecutor} implementation that uses
|
||||
* <a href="http://jakarta.apache.org/commons/httpclient">Jakarta Commons HttpClient</a>
|
||||
* to execute POST requests. Requires Commons HttpClient 3.0 or higher.
|
||||
*
|
||||
* <p>Allows to use a pre-configured {@link org.apache.commons.httpclient.HttpClient}
|
||||
* instance, potentially with authentication, HTTP connection pooling, etc.
|
||||
* Also designed for easy subclassing, providing specific template methods.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Mark Fisher
|
||||
* @since 1.1
|
||||
* @see SimpleHttpInvokerRequestExecutor
|
||||
*/
|
||||
public class CommonsHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor {
|
||||
|
||||
/**
|
||||
* Default timeout value if no HttpClient is explicitly provided.
|
||||
*/
|
||||
private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);
|
||||
|
||||
private HttpClient httpClient;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new CommonsHttpInvokerRequestExecutor with a default
|
||||
* HttpClient that uses a default MultiThreadedHttpConnectionManager.
|
||||
* Sets the socket read timeout to {@link #DEFAULT_READ_TIMEOUT_MILLISECONDS}.
|
||||
* @see org.apache.commons.httpclient.HttpClient
|
||||
* @see org.apache.commons.httpclient.MultiThreadedHttpConnectionManager
|
||||
*/
|
||||
public CommonsHttpInvokerRequestExecutor() {
|
||||
this.httpClient = new HttpClient(new MultiThreadedHttpConnectionManager());
|
||||
setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new CommonsHttpInvokerRequestExecutor with the given
|
||||
* HttpClient instance. The socket read timeout of the provided
|
||||
* HttpClient will not be changed.
|
||||
* @param httpClient the HttpClient instance to use for this request executor
|
||||
*/
|
||||
public CommonsHttpInvokerRequestExecutor(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the HttpClient instance to use for this request executor.
|
||||
*/
|
||||
public void setHttpClient(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HttpClient instance that this request executor uses.
|
||||
*/
|
||||
public HttpClient getHttpClient() {
|
||||
return this.httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection timeout for the underlying HttpClient.
|
||||
* A timeout value of 0 specifies an infinite timeout.
|
||||
* @param timeout the timeout value in milliseconds
|
||||
* @see org.apache.commons.httpclient.params.HttpConnectionManagerParams#setConnectionTimeout(int)
|
||||
*/
|
||||
public void setConnectTimeout(int timeout) {
|
||||
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
|
||||
this.httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the socket read timeout for the underlying HttpClient.
|
||||
* A timeout value of 0 specifies an infinite timeout.
|
||||
* @param timeout the timeout value in milliseconds
|
||||
* @see org.apache.commons.httpclient.params.HttpConnectionManagerParams#setSoTimeout(int)
|
||||
* @see #DEFAULT_READ_TIMEOUT_MILLISECONDS
|
||||
*/
|
||||
public void setReadTimeout(int timeout) {
|
||||
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
|
||||
this.httpClient.getHttpConnectionManager().getParams().setSoTimeout(timeout);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute the given request through Commons HttpClient.
|
||||
* <p>This method implements the basic processing workflow:
|
||||
* The actual work happens in this class's template methods.
|
||||
* @see #createPostMethod
|
||||
* @see #setRequestBody
|
||||
* @see #executePostMethod
|
||||
* @see #validateResponse
|
||||
* @see #getResponseBody
|
||||
*/
|
||||
@Override
|
||||
protected RemoteInvocationResult doExecuteRequest(
|
||||
HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
|
||||
throws IOException, ClassNotFoundException {
|
||||
|
||||
PostMethod postMethod = createPostMethod(config);
|
||||
try {
|
||||
setRequestBody(config, postMethod, baos);
|
||||
executePostMethod(config, getHttpClient(), postMethod);
|
||||
validateResponse(config, postMethod);
|
||||
InputStream responseBody = getResponseBody(config, postMethod);
|
||||
return readRemoteInvocationResult(responseBody, config.getCodebaseUrl());
|
||||
}
|
||||
finally {
|
||||
// Need to explicitly release because it might be pooled.
|
||||
postMethod.releaseConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a PostMethod for the given configuration.
|
||||
* <p>The default implementation creates a standard PostMethod with
|
||||
* "application/x-java-serialized-object" as "Content-Type" header.
|
||||
* @param config the HTTP invoker configuration that specifies the
|
||||
* target service
|
||||
* @return the PostMethod instance
|
||||
* @throws IOException if thrown by I/O methods
|
||||
*/
|
||||
protected PostMethod createPostMethod(HttpInvokerClientConfiguration config) throws IOException {
|
||||
PostMethod postMethod = new PostMethod(config.getServiceUrl());
|
||||
LocaleContext locale = LocaleContextHolder.getLocaleContext();
|
||||
if (locale != null) {
|
||||
postMethod.addRequestHeader(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale.getLocale()));
|
||||
}
|
||||
if (isAcceptGzipEncoding()) {
|
||||
postMethod.addRequestHeader(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
|
||||
}
|
||||
return postMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given serialized remote invocation as request body.
|
||||
* <p>The default implementation simply sets the serialized invocation as the
|
||||
* PostMethod's request body. This can be overridden, for example, to write a
|
||||
* specific encoding and to potentially set appropriate HTTP request headers.
|
||||
* @param config the HTTP invoker configuration that specifies the target service
|
||||
* @param postMethod the PostMethod to set the request body on
|
||||
* @param baos the ByteArrayOutputStream that contains the serialized
|
||||
* RemoteInvocation object
|
||||
* @throws IOException if thrown by I/O methods
|
||||
* @see org.apache.commons.httpclient.methods.PostMethod#setRequestBody(java.io.InputStream)
|
||||
* @see org.apache.commons.httpclient.methods.PostMethod#setRequestEntity
|
||||
* @see org.apache.commons.httpclient.methods.InputStreamRequestEntity
|
||||
*/
|
||||
protected void setRequestBody(
|
||||
HttpInvokerClientConfiguration config, PostMethod postMethod, ByteArrayOutputStream baos)
|
||||
throws IOException {
|
||||
|
||||
postMethod.setRequestEntity(new ByteArrayRequestEntity(baos.toByteArray(), getContentType()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given PostMethod instance.
|
||||
* @param config the HTTP invoker configuration that specifies the target service
|
||||
* @param httpClient the HttpClient to execute on
|
||||
* @param postMethod the PostMethod to execute
|
||||
* @throws IOException if thrown by I/O methods
|
||||
* @see org.apache.commons.httpclient.HttpClient#executeMethod(org.apache.commons.httpclient.HttpMethod)
|
||||
*/
|
||||
protected void executePostMethod(
|
||||
HttpInvokerClientConfiguration config, HttpClient httpClient, PostMethod postMethod)
|
||||
throws IOException {
|
||||
|
||||
httpClient.executeMethod(postMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given response as contained in the PostMethod object,
|
||||
* throwing an exception if it does not correspond to a successful HTTP response.
|
||||
* <p>Default implementation rejects any HTTP status code beyond 2xx, to avoid
|
||||
* parsing the response body and trying to deserialize from a corrupted stream.
|
||||
* @param config the HTTP invoker configuration that specifies the target service
|
||||
* @param postMethod the executed PostMethod to validate
|
||||
* @throws IOException if validation failed
|
||||
* @see org.apache.commons.httpclient.methods.PostMethod#getStatusCode()
|
||||
* @see org.apache.commons.httpclient.HttpException
|
||||
*/
|
||||
protected void validateResponse(HttpInvokerClientConfiguration config, PostMethod postMethod)
|
||||
throws IOException {
|
||||
|
||||
if (postMethod.getStatusCode() >= 300) {
|
||||
throw new HttpException(
|
||||
"Did not receive successful HTTP response: status code = " + postMethod.getStatusCode() +
|
||||
", status message = [" + postMethod.getStatusText() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the response body from the given executed remote invocation request.
|
||||
* <p>The default implementation simply fetches the PostMethod's response body stream.
|
||||
* If the response is recognized as GZIP response, the InputStream will get wrapped
|
||||
* in a GZIPInputStream.
|
||||
* @param config the HTTP invoker configuration that specifies the target service
|
||||
* @param postMethod the PostMethod to read the response body from
|
||||
* @return an InputStream for the response body
|
||||
* @throws IOException if thrown by I/O methods
|
||||
* @see #isGzipResponse
|
||||
* @see java.util.zip.GZIPInputStream
|
||||
* @see org.apache.commons.httpclient.methods.PostMethod#getResponseBodyAsStream()
|
||||
* @see org.apache.commons.httpclient.methods.PostMethod#getResponseHeader(String)
|
||||
*/
|
||||
protected InputStream getResponseBody(HttpInvokerClientConfiguration config, PostMethod postMethod)
|
||||
throws IOException {
|
||||
|
||||
if (isGzipResponse(postMethod)) {
|
||||
return new GZIPInputStream(postMethod.getResponseBodyAsStream());
|
||||
}
|
||||
else {
|
||||
return postMethod.getResponseBodyAsStream();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given response indicates a GZIP response.
|
||||
* <p>The default implementation checks whether the HTTP "Content-Encoding"
|
||||
* header contains "gzip" (in any casing).
|
||||
* @param postMethod the PostMethod to check
|
||||
* @return whether the given response indicates a GZIP response
|
||||
*/
|
||||
protected boolean isGzipResponse(PostMethod postMethod) {
|
||||
Header encodingHeader = postMethod.getResponseHeader(HTTP_HEADER_CONTENT_ENCODING);
|
||||
return (encodingHeader != null && encodingHeader.getValue() != null &&
|
||||
encodingHeader.getValue().toLowerCase().contains(ENCODING_GZIP));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.httpinvoker;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NoHttpResponseException;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||
import org.apache.http.conn.scheme.Scheme;
|
||||
import org.apache.http.conn.scheme.SchemeRegistry;
|
||||
import org.apache.http.conn.ssl.SSLSocketFactory;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||
import org.apache.http.params.CoreConnectionPNames;
|
||||
|
||||
import org.springframework.context.i18n.LocaleContext;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.remoting.support.RemoteInvocationResult;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.remoting.httpinvoker.HttpInvokerRequestExecutor} implementation that uses
|
||||
* <a href="http://hc.apache.org/httpcomponents-client-ga/httpclient/">Apache HttpComponents HttpClient</a>
|
||||
* to execute POST requests.
|
||||
*
|
||||
* <p>Allows to use a pre-configured {@link org.apache.http.client.HttpClient}
|
||||
* instance, potentially with authentication, HTTP connection pooling, etc.
|
||||
* Also designed for easy subclassing, providing specific template methods.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.1
|
||||
* @see org.springframework.remoting.httpinvoker.SimpleHttpInvokerRequestExecutor
|
||||
*/
|
||||
public class HttpComponentsHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor {
|
||||
|
||||
private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 100;
|
||||
|
||||
private static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 5;
|
||||
|
||||
private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);
|
||||
|
||||
private HttpClient httpClient;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance of the HttpComponentsHttpInvokerRequestExecutor with a default
|
||||
* {@link HttpClient} that uses a default {@link org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager}.
|
||||
*/
|
||||
public HttpComponentsHttpInvokerRequestExecutor() {
|
||||
SchemeRegistry schemeRegistry = new SchemeRegistry();
|
||||
schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
|
||||
schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));
|
||||
|
||||
ThreadSafeClientConnManager connectionManager = new ThreadSafeClientConnManager(schemeRegistry);
|
||||
connectionManager.setMaxTotal(DEFAULT_MAX_TOTAL_CONNECTIONS);
|
||||
connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTIONS_PER_ROUTE);
|
||||
|
||||
this.httpClient = new DefaultHttpClient(connectionManager);
|
||||
setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of the HttpComponentsClientHttpRequestFactory
|
||||
* with the given {@link HttpClient} instance.
|
||||
* @param httpClient the HttpClient instance to use for this request executor
|
||||
*/
|
||||
public HttpComponentsHttpInvokerRequestExecutor(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the {@link HttpClient} instance to use for this request executor.
|
||||
*/
|
||||
public void setHttpClient(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link HttpClient} instance that this request executor uses.
|
||||
*/
|
||||
public HttpClient getHttpClient() {
|
||||
return this.httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection timeout for the underlying HttpClient.
|
||||
* A timeout value of 0 specifies an infinite timeout.
|
||||
* @param timeout the timeout value in milliseconds
|
||||
*/
|
||||
public void setConnectTimeout(int timeout) {
|
||||
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
|
||||
getHttpClient().getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the socket read timeout for the underlying HttpClient.
|
||||
* A timeout value of 0 specifies an infinite timeout.
|
||||
* @param timeout the timeout value in milliseconds
|
||||
* @see #DEFAULT_READ_TIMEOUT_MILLISECONDS
|
||||
*/
|
||||
public void setReadTimeout(int timeout) {
|
||||
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
|
||||
getHttpClient().getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, timeout);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute the given request through the HttpClient.
|
||||
* <p>This method implements the basic processing workflow:
|
||||
* The actual work happens in this class's template methods.
|
||||
* @see #createHttpPost
|
||||
* @see #setRequestBody
|
||||
* @see #executeHttpPost
|
||||
* @see #validateResponse
|
||||
* @see #getResponseBody
|
||||
*/
|
||||
@Override
|
||||
protected RemoteInvocationResult doExecuteRequest(
|
||||
HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
|
||||
throws IOException, ClassNotFoundException {
|
||||
|
||||
HttpPost postMethod = createHttpPost(config);
|
||||
setRequestBody(config, postMethod, baos);
|
||||
HttpResponse response = executeHttpPost(config, getHttpClient(), postMethod);
|
||||
validateResponse(config, response);
|
||||
InputStream responseBody = getResponseBody(config, response);
|
||||
return readRemoteInvocationResult(responseBody, config.getCodebaseUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a HttpPost for the given configuration.
|
||||
* <p>The default implementation creates a standard HttpPost with
|
||||
* "application/x-java-serialized-object" as "Content-Type" header.
|
||||
* @param config the HTTP invoker configuration that specifies the
|
||||
* target service
|
||||
* @return the HttpPost instance
|
||||
* @throws java.io.IOException if thrown by I/O methods
|
||||
*/
|
||||
protected HttpPost createHttpPost(HttpInvokerClientConfiguration config) throws IOException {
|
||||
HttpPost httpPost = new HttpPost(config.getServiceUrl());
|
||||
LocaleContext locale = LocaleContextHolder.getLocaleContext();
|
||||
if (locale != null) {
|
||||
httpPost.addHeader(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale.getLocale()));
|
||||
}
|
||||
if (isAcceptGzipEncoding()) {
|
||||
httpPost.addHeader(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
|
||||
}
|
||||
return httpPost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given serialized remote invocation as request body.
|
||||
* <p>The default implementation simply sets the serialized invocation as the
|
||||
* HttpPost's request body. This can be overridden, for example, to write a
|
||||
* specific encoding and to potentially set appropriate HTTP request headers.
|
||||
* @param config the HTTP invoker configuration that specifies the target service
|
||||
* @param httpPost the HttpPost to set the request body on
|
||||
* @param baos the ByteArrayOutputStream that contains the serialized
|
||||
* RemoteInvocation object
|
||||
* @throws java.io.IOException if thrown by I/O methods
|
||||
*/
|
||||
protected void setRequestBody(
|
||||
HttpInvokerClientConfiguration config, HttpPost httpPost, ByteArrayOutputStream baos)
|
||||
throws IOException {
|
||||
|
||||
ByteArrayEntity entity = new ByteArrayEntity(baos.toByteArray());
|
||||
entity.setContentType(getContentType());
|
||||
httpPost.setEntity(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given HttpPost instance.
|
||||
* @param config the HTTP invoker configuration that specifies the target service
|
||||
* @param httpClient the HttpClient to execute on
|
||||
* @param httpPost the HttpPost to execute
|
||||
* @return the resulting HttpResponse
|
||||
* @throws java.io.IOException if thrown by I/O methods
|
||||
*/
|
||||
protected HttpResponse executeHttpPost(
|
||||
HttpInvokerClientConfiguration config, HttpClient httpClient, HttpPost httpPost)
|
||||
throws IOException {
|
||||
|
||||
return httpClient.execute(httpPost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given response as contained in the HttpPost object,
|
||||
* throwing an exception if it does not correspond to a successful HTTP response.
|
||||
* <p>Default implementation rejects any HTTP status code beyond 2xx, to avoid
|
||||
* parsing the response body and trying to deserialize from a corrupted stream.
|
||||
* @param config the HTTP invoker configuration that specifies the target service
|
||||
* @param response the resulting HttpResponse to validate
|
||||
* @throws java.io.IOException if validation failed
|
||||
*/
|
||||
protected void validateResponse(HttpInvokerClientConfiguration config, HttpResponse response)
|
||||
throws IOException {
|
||||
|
||||
StatusLine status = response.getStatusLine();
|
||||
if (status.getStatusCode() >= 300) {
|
||||
throw new NoHttpResponseException(
|
||||
"Did not receive successful HTTP response: status code = " + status.getStatusCode() +
|
||||
", status message = [" + status.getReasonPhrase() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the response body from the given executed remote invocation request.
|
||||
* <p>The default implementation simply fetches the HttpPost's response body stream.
|
||||
* If the response is recognized as GZIP response, the InputStream will get wrapped
|
||||
* in a GZIPInputStream.
|
||||
* @param config the HTTP invoker configuration that specifies the target service
|
||||
* @param httpResponse the resulting HttpResponse to read the response body from
|
||||
* @return an InputStream for the response body
|
||||
* @throws java.io.IOException if thrown by I/O methods
|
||||
* @see #isGzipResponse
|
||||
* @see java.util.zip.GZIPInputStream
|
||||
*/
|
||||
protected InputStream getResponseBody(HttpInvokerClientConfiguration config, HttpResponse httpResponse)
|
||||
throws IOException {
|
||||
|
||||
if (isGzipResponse(httpResponse)) {
|
||||
return new GZIPInputStream(httpResponse.getEntity().getContent());
|
||||
}
|
||||
else {
|
||||
return httpResponse.getEntity().getContent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given response indicates a GZIP response.
|
||||
* <p>The default implementation checks whether the HTTP "Content-Encoding"
|
||||
* header contains "gzip" (in any casing).
|
||||
* @param httpResponse the resulting HttpResponse to check
|
||||
* @return whether the given response indicates a GZIP response
|
||||
*/
|
||||
protected boolean isGzipResponse(HttpResponse httpResponse) {
|
||||
Header encodingHeader = httpResponse.getFirstHeader(HTTP_HEADER_CONTENT_ENCODING);
|
||||
return (encodingHeader != null && encodingHeader.getValue() != null &&
|
||||
encodingHeader.getValue().toLowerCase().contains(ENCODING_GZIP));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2002-2005 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.httpinvoker;
|
||||
|
||||
/**
|
||||
* Configuration interface for executing HTTP invoker requests.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
* @see HttpInvokerRequestExecutor
|
||||
* @see HttpInvokerClientInterceptor
|
||||
*/
|
||||
public interface HttpInvokerClientConfiguration {
|
||||
|
||||
/**
|
||||
* Return the HTTP URL of the target service.
|
||||
*/
|
||||
String getServiceUrl();
|
||||
|
||||
/**
|
||||
* Return the codebase URL to download classes from if not found locally.
|
||||
* Can consist of multiple URLs, separated by spaces.
|
||||
* @return the codebase URL, or <code>null</code> if none
|
||||
* @see java.rmi.server.RMIClassLoader
|
||||
*/
|
||||
String getCodebaseUrl();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.httpinvoker;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InvalidClassException;
|
||||
import java.net.ConnectException;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.remoting.RemoteAccessException;
|
||||
import org.springframework.remoting.RemoteConnectFailureException;
|
||||
import org.springframework.remoting.RemoteInvocationFailureException;
|
||||
import org.springframework.remoting.support.RemoteInvocation;
|
||||
import org.springframework.remoting.support.RemoteInvocationBasedAccessor;
|
||||
import org.springframework.remoting.support.RemoteInvocationResult;
|
||||
|
||||
/**
|
||||
* {@link org.aopalliance.intercept.MethodInterceptor} for accessing an
|
||||
* HTTP invoker service. The service URL must be an HTTP URL exposing
|
||||
* an HTTP invoker service.
|
||||
*
|
||||
* <p>Serializes remote invocation objects and deserializes remote invocation
|
||||
* result objects. Uses Java serialization just like RMI, but provides the
|
||||
* same ease of setup as Caucho's HTTP-based Hessian and Burlap protocols.
|
||||
*
|
||||
* <P>HTTP invoker is a very extensible and customizable protocol.
|
||||
* It supports the RemoteInvocationFactory mechanism, like RMI invoker,
|
||||
* allowing to include additional invocation attributes (for example,
|
||||
* a security context). Furthermore, it allows to customize request
|
||||
* execution via the {@link HttpInvokerRequestExecutor} strategy.
|
||||
*
|
||||
* <p>Can use the JDK's {@link java.rmi.server.RMIClassLoader} to load
|
||||
* classes from a given {@link #setCodebaseUrl codebase}, performing
|
||||
* on-demand dynamic code download from a remote location. The codebase
|
||||
* can consist of multiple URLs, separated by spaces. Note that
|
||||
* RMIClassLoader requires a SecurityManager to be set, analogous to
|
||||
* when using dynamic class download with standard RMI!
|
||||
* (See the RMI documentation for details.)
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
* @see #setServiceUrl
|
||||
* @see #setCodebaseUrl
|
||||
* @see #setRemoteInvocationFactory
|
||||
* @see #setHttpInvokerRequestExecutor
|
||||
* @see HttpInvokerServiceExporter
|
||||
* @see HttpInvokerProxyFactoryBean
|
||||
* @see java.rmi.server.RMIClassLoader
|
||||
*/
|
||||
public class HttpInvokerClientInterceptor extends RemoteInvocationBasedAccessor
|
||||
implements MethodInterceptor, HttpInvokerClientConfiguration {
|
||||
|
||||
private String codebaseUrl;
|
||||
|
||||
private HttpInvokerRequestExecutor httpInvokerRequestExecutor;
|
||||
|
||||
|
||||
/**
|
||||
* Set the codebase URL to download classes from if not found locally.
|
||||
* Can consists of multiple URLs, separated by spaces.
|
||||
* <p>Follows RMI's codebase conventions for dynamic class download.
|
||||
* In contrast to RMI, where the server determines the URL for class download
|
||||
* (via the "java.rmi.server.codebase" system property), it's the client
|
||||
* that determines the codebase URL here. The server will usually be the
|
||||
* same as for the service URL, just pointing to a different path there.
|
||||
* @see #setServiceUrl
|
||||
* @see org.springframework.remoting.rmi.CodebaseAwareObjectInputStream
|
||||
* @see java.rmi.server.RMIClassLoader
|
||||
*/
|
||||
public void setCodebaseUrl(String codebaseUrl) {
|
||||
this.codebaseUrl = codebaseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the codebase URL to download classes from if not found locally.
|
||||
*/
|
||||
public String getCodebaseUrl() {
|
||||
return this.codebaseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the HttpInvokerRequestExecutor implementation to use for executing
|
||||
* remote invocations.
|
||||
* <p>Default is {@link SimpleHttpInvokerRequestExecutor}. Alternatively,
|
||||
* consider using {@link CommonsHttpInvokerRequestExecutor} for more
|
||||
* sophisticated needs.
|
||||
* @see SimpleHttpInvokerRequestExecutor
|
||||
* @see CommonsHttpInvokerRequestExecutor
|
||||
*/
|
||||
public void setHttpInvokerRequestExecutor(HttpInvokerRequestExecutor httpInvokerRequestExecutor) {
|
||||
this.httpInvokerRequestExecutor = httpInvokerRequestExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HttpInvokerRequestExecutor used by this remote accessor.
|
||||
* <p>Creates a default SimpleHttpInvokerRequestExecutor if no executor
|
||||
* has been initialized already.
|
||||
*/
|
||||
public HttpInvokerRequestExecutor getHttpInvokerRequestExecutor() {
|
||||
if (this.httpInvokerRequestExecutor == null) {
|
||||
SimpleHttpInvokerRequestExecutor executor = new SimpleHttpInvokerRequestExecutor();
|
||||
executor.setBeanClassLoader(getBeanClassLoader());
|
||||
this.httpInvokerRequestExecutor = executor;
|
||||
}
|
||||
return this.httpInvokerRequestExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
|
||||
// Eagerly initialize the default HttpInvokerRequestExecutor, if needed.
|
||||
getHttpInvokerRequestExecutor();
|
||||
}
|
||||
|
||||
|
||||
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
|
||||
if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
|
||||
return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]";
|
||||
}
|
||||
|
||||
RemoteInvocation invocation = createRemoteInvocation(methodInvocation);
|
||||
RemoteInvocationResult result = null;
|
||||
try {
|
||||
result = executeRequest(invocation, methodInvocation);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw convertHttpInvokerAccessException(ex);
|
||||
}
|
||||
try {
|
||||
return recreateRemoteInvocationResult(result);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
if (result.hasInvocationTargetException()) {
|
||||
throw ex;
|
||||
}
|
||||
else {
|
||||
throw new RemoteInvocationFailureException("Invocation of method [" + methodInvocation.getMethod() +
|
||||
"] failed in HTTP invoker remote service at [" + getServiceUrl() + "]", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given remote invocation via the HttpInvokerRequestExecutor.
|
||||
* <p>This implementation delegates to {@link #executeRequest(RemoteInvocation)}.
|
||||
* Can be overridden to react to the specific original MethodInvocation.
|
||||
* @param invocation the RemoteInvocation to execute
|
||||
* @param originalInvocation the original MethodInvocation (can e.g. be cast
|
||||
* to the ProxyMethodInvocation interface for accessing user attributes)
|
||||
* @return the RemoteInvocationResult object
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
protected RemoteInvocationResult executeRequest(
|
||||
RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception {
|
||||
|
||||
return executeRequest(invocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given remote invocation via the HttpInvokerRequestExecutor.
|
||||
* <p>Can be overridden in subclasses to pass a different configuration object
|
||||
* to the executor. Alternatively, add further configuration properties in a
|
||||
* subclass of this accessor: By default, the accessor passed itself as
|
||||
* configuration object to the executor.
|
||||
* @param invocation the RemoteInvocation to execute
|
||||
* @return the RemoteInvocationResult object
|
||||
* @throws IOException if thrown by I/O operations
|
||||
* @throws ClassNotFoundException if thrown during deserialization
|
||||
* @throws Exception in case of general errors
|
||||
* @see #getHttpInvokerRequestExecutor
|
||||
* @see HttpInvokerClientConfiguration
|
||||
*/
|
||||
protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws Exception {
|
||||
return getHttpInvokerRequestExecutor().executeRequest(this, invocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given HTTP invoker access exception to an appropriate
|
||||
* Spring RemoteAccessException.
|
||||
* @param ex the exception to convert
|
||||
* @return the RemoteAccessException to throw
|
||||
*/
|
||||
protected RemoteAccessException convertHttpInvokerAccessException(Throwable ex) {
|
||||
if (ex instanceof ConnectException) {
|
||||
throw new RemoteConnectFailureException(
|
||||
"Could not connect to HTTP invoker remote service at [" + getServiceUrl() + "]", ex);
|
||||
}
|
||||
else if (ex instanceof ClassNotFoundException || ex instanceof NoClassDefFoundError ||
|
||||
ex instanceof InvalidClassException) {
|
||||
throw new RemoteAccessException(
|
||||
"Could not deserialize result from HTTP invoker remote service [" + getServiceUrl() + "]", ex);
|
||||
}
|
||||
else {
|
||||
throw new RemoteAccessException(
|
||||
"Could not access HTTP invoker remote service at [" + getServiceUrl() + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.httpinvoker;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
|
||||
/**
|
||||
* {@link FactoryBean} for HTTP invoker proxies. Exposes the proxied service
|
||||
* for use as a bean reference, using the specified service interface.
|
||||
*
|
||||
* <p>The service URL must be an HTTP URL exposing an HTTP invoker service.
|
||||
* Optionally, a codebase URL can be specified for on-demand dynamic code download
|
||||
* from a remote location. For details, see HttpInvokerClientInterceptor docs.
|
||||
*
|
||||
* <p>Serializes remote invocation objects and deserializes remote invocation
|
||||
* result objects. Uses Java serialization just like RMI, but provides the
|
||||
* same ease of setup as Caucho's HTTP-based Hessian and Burlap protocols.
|
||||
*
|
||||
* <p><b>HTTP invoker is the recommended protocol for Java-to-Java remoting.</b>
|
||||
* It is more powerful and more extensible than Hessian and Burlap, at the
|
||||
* expense of being tied to Java. Nevertheless, it is as easy to set up as
|
||||
* Hessian and Burlap, which is its main advantage compared to RMI.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
* @see #setServiceInterface
|
||||
* @see #setServiceUrl
|
||||
* @see #setCodebaseUrl
|
||||
* @see HttpInvokerClientInterceptor
|
||||
* @see HttpInvokerServiceExporter
|
||||
* @see org.springframework.remoting.rmi.RmiProxyFactoryBean
|
||||
* @see org.springframework.remoting.caucho.HessianProxyFactoryBean
|
||||
* @see org.springframework.remoting.caucho.BurlapProxyFactoryBean
|
||||
*/
|
||||
public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor
|
||||
implements FactoryBean<Object> {
|
||||
|
||||
private Object serviceProxy;
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
if (getServiceInterface() == null) {
|
||||
throw new IllegalArgumentException("Property 'serviceInterface' is required");
|
||||
}
|
||||
this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
|
||||
}
|
||||
|
||||
|
||||
public Object getObject() {
|
||||
return this.serviceProxy;
|
||||
}
|
||||
|
||||
public Class<?> getObjectType() {
|
||||
return getServiceInterface();
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2002-2005 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.httpinvoker;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.remoting.support.RemoteInvocation;
|
||||
import org.springframework.remoting.support.RemoteInvocationResult;
|
||||
|
||||
/**
|
||||
* Strategy interface for actual execution of an HTTP invoker request.
|
||||
* Used by HttpInvokerClientInterceptor and its subclass
|
||||
* HttpInvokerProxyFactoryBean.
|
||||
*
|
||||
* <p>Two implementations are provided out of the box:
|
||||
* <ul>
|
||||
* <li><b>SimpleHttpInvokerRequestExecutor:</b>
|
||||
* Uses J2SE facilities to execute POST requests, without support
|
||||
* for HTTP authentication or advanced configuration options.
|
||||
* <li><b>CommonsHttpInvokerRequestExecutor:</b>
|
||||
* Uses Jakarta's Commons HttpClient to execute POST requests,
|
||||
* allowing to use a preconfigured HttpClient instance
|
||||
* (potentially with authentication, HTTP connection pooling, etc).
|
||||
* </ul>
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
* @see HttpInvokerClientInterceptor#setHttpInvokerRequestExecutor
|
||||
*/
|
||||
public interface HttpInvokerRequestExecutor {
|
||||
|
||||
/**
|
||||
* Execute a request to send the given remote invocation.
|
||||
* @param config the HTTP invoker configuration that specifies the
|
||||
* target service
|
||||
* @param invocation the RemoteInvocation to execute
|
||||
* @return the RemoteInvocationResult object
|
||||
* @throws IOException if thrown by I/O operations
|
||||
* @throws ClassNotFoundException if thrown during deserialization
|
||||
* @throws Exception in case of general errors
|
||||
*/
|
||||
RemoteInvocationResult executeRequest(HttpInvokerClientConfiguration config, RemoteInvocation invocation)
|
||||
throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.httpinvoker;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.remoting.rmi.RemoteInvocationSerializingExporter;
|
||||
import org.springframework.remoting.support.RemoteInvocation;
|
||||
import org.springframework.remoting.support.RemoteInvocationResult;
|
||||
import org.springframework.web.HttpRequestHandler;
|
||||
import org.springframework.web.util.NestedServletException;
|
||||
|
||||
/**
|
||||
* Servlet-API-based HTTP request handler that exports the specified service bean
|
||||
* as HTTP invoker service endpoint, accessible via an HTTP invoker proxy.
|
||||
*
|
||||
* <p><b>Note:</b> Spring also provides an alternative version of this exporter,
|
||||
* for Sun's JRE 1.6 HTTP server: {@link SimpleHttpInvokerServiceExporter}.
|
||||
*
|
||||
* <p>Deserializes remote invocation objects and serializes remote invocation
|
||||
* result objects. Uses Java serialization just like RMI, but provides the
|
||||
* same ease of setup as Caucho's HTTP-based Hessian and Burlap protocols.
|
||||
*
|
||||
* <p><b>HTTP invoker is the recommended protocol for Java-to-Java remoting.</b>
|
||||
* It is more powerful and more extensible than Hessian and Burlap, at the
|
||||
* expense of being tied to Java. Nevertheless, it is as easy to set up as
|
||||
* Hessian and Burlap, which is its main advantage compared to RMI.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
* @see HttpInvokerClientInterceptor
|
||||
* @see HttpInvokerProxyFactoryBean
|
||||
* @see org.springframework.remoting.rmi.RmiServiceExporter
|
||||
* @see org.springframework.remoting.caucho.HessianServiceExporter
|
||||
* @see org.springframework.remoting.caucho.BurlapServiceExporter
|
||||
*/
|
||||
public class HttpInvokerServiceExporter extends RemoteInvocationSerializingExporter
|
||||
implements HttpRequestHandler {
|
||||
|
||||
/**
|
||||
* Reads a remote invocation from the request, executes it,
|
||||
* and writes the remote invocation result to the response.
|
||||
* @see #readRemoteInvocation(HttpServletRequest)
|
||||
* @see #invokeAndCreateResult(org.springframework.remoting.support.RemoteInvocation, Object)
|
||||
* @see #writeRemoteInvocationResult(HttpServletRequest, HttpServletResponse, RemoteInvocationResult)
|
||||
*/
|
||||
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
|
||||
try {
|
||||
RemoteInvocation invocation = readRemoteInvocation(request);
|
||||
RemoteInvocationResult result = invokeAndCreateResult(invocation, getProxy());
|
||||
writeRemoteInvocationResult(request, response, result);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new NestedServletException("Class not found during deserialization", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a RemoteInvocation from the given HTTP request.
|
||||
* <p>Delegates to
|
||||
* {@link #readRemoteInvocation(javax.servlet.http.HttpServletRequest, java.io.InputStream)}
|
||||
* with the
|
||||
* {@link javax.servlet.ServletRequest#getInputStream() servlet request's input stream}.
|
||||
* @param request current HTTP request
|
||||
* @return the RemoteInvocation object
|
||||
* @throws IOException in case of I/O failure
|
||||
* @throws ClassNotFoundException if thrown by deserialization
|
||||
*/
|
||||
protected RemoteInvocation readRemoteInvocation(HttpServletRequest request)
|
||||
throws IOException, ClassNotFoundException {
|
||||
|
||||
return readRemoteInvocation(request, request.getInputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a RemoteInvocation object from the given InputStream.
|
||||
* <p>Gives {@link #decorateInputStream} a chance to decorate the stream
|
||||
* first (for example, for custom encryption or compression). Creates a
|
||||
* {@link org.springframework.remoting.rmi.CodebaseAwareObjectInputStream}
|
||||
* and calls {@link #doReadRemoteInvocation} to actually read the object.
|
||||
* <p>Can be overridden for custom serialization of the invocation.
|
||||
* @param request current HTTP request
|
||||
* @param is the InputStream to read from
|
||||
* @return the RemoteInvocation object
|
||||
* @throws IOException in case of I/O failure
|
||||
* @throws ClassNotFoundException if thrown during deserialization
|
||||
*/
|
||||
protected RemoteInvocation readRemoteInvocation(HttpServletRequest request, InputStream is)
|
||||
throws IOException, ClassNotFoundException {
|
||||
|
||||
ObjectInputStream ois = createObjectInputStream(decorateInputStream(request, is));
|
||||
try {
|
||||
return doReadRemoteInvocation(ois);
|
||||
}
|
||||
finally {
|
||||
ois.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the InputStream to use for reading remote invocations,
|
||||
* potentially decorating the given original InputStream.
|
||||
* <p>The default implementation returns the given stream as-is.
|
||||
* Can be overridden, for example, for custom encryption or compression.
|
||||
* @param request current HTTP request
|
||||
* @param is the original InputStream
|
||||
* @return the potentially decorated InputStream
|
||||
* @throws IOException in case of I/O failure
|
||||
*/
|
||||
protected InputStream decorateInputStream(HttpServletRequest request, InputStream is) throws IOException {
|
||||
return is;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given RemoteInvocationResult to the given HTTP response.
|
||||
* @param request current HTTP request
|
||||
* @param response current HTTP response
|
||||
* @param result the RemoteInvocationResult object
|
||||
* @throws IOException in case of I/O failure
|
||||
*/
|
||||
protected void writeRemoteInvocationResult(
|
||||
HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result)
|
||||
throws IOException {
|
||||
|
||||
response.setContentType(getContentType());
|
||||
writeRemoteInvocationResult(request, response, result, response.getOutputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the given RemoteInvocation to the given OutputStream.
|
||||
* <p>The default implementation gives {@link #decorateOutputStream} a chance
|
||||
* to decorate the stream first (for example, for custom encryption or compression).
|
||||
* Creates an {@link java.io.ObjectOutputStream} for the final stream and calls
|
||||
* {@link #doWriteRemoteInvocationResult} to actually write the object.
|
||||
* <p>Can be overridden for custom serialization of the invocation.
|
||||
* @param request current HTTP request
|
||||
* @param response current HTTP response
|
||||
* @param result the RemoteInvocationResult object
|
||||
* @param os the OutputStream to write to
|
||||
* @throws IOException in case of I/O failure
|
||||
* @see #decorateOutputStream
|
||||
* @see #doWriteRemoteInvocationResult
|
||||
*/
|
||||
protected void writeRemoteInvocationResult(
|
||||
HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result, OutputStream os)
|
||||
throws IOException {
|
||||
|
||||
ObjectOutputStream oos = createObjectOutputStream(decorateOutputStream(request, response, os));
|
||||
try {
|
||||
doWriteRemoteInvocationResult(result, oos);
|
||||
}
|
||||
finally {
|
||||
oos.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the OutputStream to use for writing remote invocation results,
|
||||
* potentially decorating the given original OutputStream.
|
||||
* <p>The default implementation returns the given stream as-is.
|
||||
* Can be overridden, for example, for custom encryption or compression.
|
||||
* @param request current HTTP request
|
||||
* @param response current HTTP response
|
||||
* @param os the original OutputStream
|
||||
* @return the potentially decorated OutputStream
|
||||
* @throws IOException in case of I/O failure
|
||||
*/
|
||||
protected OutputStream decorateOutputStream(
|
||||
HttpServletRequest request, HttpServletResponse response, OutputStream os) throws IOException {
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.httpinvoker;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import org.springframework.context.i18n.LocaleContext;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.remoting.support.RemoteInvocationResult;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* HttpInvokerRequestExecutor implementation that uses standard J2SE facilities
|
||||
* to execute POST requests, without support for HTTP authentication or
|
||||
* advanced configuration options.
|
||||
*
|
||||
* <p>Designed for easy subclassing, customizing specific template methods.
|
||||
* However, consider CommonsHttpInvokerRequestExecutor for more sophisticated
|
||||
* needs: The J2SE HttpURLConnection is rather limited in its capabilities.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
* @see CommonsHttpInvokerRequestExecutor
|
||||
* @see java.net.HttpURLConnection
|
||||
*/
|
||||
public class SimpleHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor {
|
||||
|
||||
private int connectTimeout = -1;
|
||||
|
||||
private int readTimeout = -1;
|
||||
|
||||
|
||||
/**
|
||||
* Set the underlying URLConnection's connect timeout (in milliseconds).
|
||||
* A timeout value of 0 specifies an infinite timeout.
|
||||
* <p>Default is the system's default timeout.
|
||||
* @see URLConnection#setConnectTimeout(int)
|
||||
*/
|
||||
public void setConnectTimeout(int connectTimeout) {
|
||||
this.connectTimeout = connectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the underlying URLConnection's read timeout (in milliseconds).
|
||||
* A timeout value of 0 specifies an infinite timeout.
|
||||
* <p>Default is the system's default timeout.
|
||||
* @see URLConnection#setReadTimeout(int)
|
||||
*/
|
||||
public void setReadTimeout(int readTimeout) {
|
||||
this.readTimeout = readTimeout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute the given request through a standard J2SE HttpURLConnection.
|
||||
* <p>This method implements the basic processing workflow:
|
||||
* The actual work happens in this class's template methods.
|
||||
* @see #openConnection
|
||||
* @see #prepareConnection
|
||||
* @see #writeRequestBody
|
||||
* @see #validateResponse
|
||||
* @see #readResponseBody
|
||||
*/
|
||||
@Override
|
||||
protected RemoteInvocationResult doExecuteRequest(
|
||||
HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
|
||||
throws IOException, ClassNotFoundException {
|
||||
|
||||
HttpURLConnection con = openConnection(config);
|
||||
prepareConnection(con, baos.size());
|
||||
writeRequestBody(config, con, baos);
|
||||
validateResponse(config, con);
|
||||
InputStream responseBody = readResponseBody(config, con);
|
||||
|
||||
return readRemoteInvocationResult(responseBody, config.getCodebaseUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Open an HttpURLConnection for the given remote invocation request.
|
||||
* @param config the HTTP invoker configuration that specifies the
|
||||
* target service
|
||||
* @return the HttpURLConnection for the given request
|
||||
* @throws IOException if thrown by I/O methods
|
||||
* @see java.net.URL#openConnection()
|
||||
*/
|
||||
protected HttpURLConnection openConnection(HttpInvokerClientConfiguration config) throws IOException {
|
||||
URLConnection con = new URL(config.getServiceUrl()).openConnection();
|
||||
if (!(con instanceof HttpURLConnection)) {
|
||||
throw new IOException("Service URL [" + config.getServiceUrl() + "] is not an HTTP URL");
|
||||
}
|
||||
return (HttpURLConnection) con;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the given HTTP connection.
|
||||
* <p>The default implementation specifies POST as method,
|
||||
* "application/x-java-serialized-object" as "Content-Type" header,
|
||||
* and the given content length as "Content-Length" header.
|
||||
* @param connection the HTTP connection to prepare
|
||||
* @param contentLength the length of the content to send
|
||||
* @throws IOException if thrown by HttpURLConnection methods
|
||||
* @see java.net.HttpURLConnection#setRequestMethod
|
||||
* @see java.net.HttpURLConnection#setRequestProperty
|
||||
*/
|
||||
protected void prepareConnection(HttpURLConnection connection, int contentLength) throws IOException {
|
||||
if (this.connectTimeout >= 0) {
|
||||
connection.setConnectTimeout(this.connectTimeout);
|
||||
}
|
||||
if (this.readTimeout >= 0) {
|
||||
connection.setReadTimeout(this.readTimeout);
|
||||
}
|
||||
connection.setDoOutput(true);
|
||||
connection.setRequestMethod(HTTP_METHOD_POST);
|
||||
connection.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, getContentType());
|
||||
connection.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH, Integer.toString(contentLength));
|
||||
LocaleContext locale = LocaleContextHolder.getLocaleContext();
|
||||
if (locale != null) {
|
||||
connection.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale.getLocale()));
|
||||
}
|
||||
if (isAcceptGzipEncoding()) {
|
||||
connection.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given serialized remote invocation as request body.
|
||||
* <p>The default implementation simply write the serialized invocation to the
|
||||
* HttpURLConnection's OutputStream. This can be overridden, for example, to write
|
||||
* a specific encoding and potentially set appropriate HTTP request headers.
|
||||
* @param config the HTTP invoker configuration that specifies the target service
|
||||
* @param con the HttpURLConnection to write the request body to
|
||||
* @param baos the ByteArrayOutputStream that contains the serialized
|
||||
* RemoteInvocation object
|
||||
* @throws IOException if thrown by I/O methods
|
||||
* @see java.net.HttpURLConnection#getOutputStream()
|
||||
* @see java.net.HttpURLConnection#setRequestProperty
|
||||
*/
|
||||
protected void writeRequestBody(
|
||||
HttpInvokerClientConfiguration config, HttpURLConnection con, ByteArrayOutputStream baos)
|
||||
throws IOException {
|
||||
|
||||
baos.writeTo(con.getOutputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given response as contained in the HttpURLConnection object,
|
||||
* throwing an exception if it does not correspond to a successful HTTP response.
|
||||
* <p>Default implementation rejects any HTTP status code beyond 2xx, to avoid
|
||||
* parsing the response body and trying to deserialize from a corrupted stream.
|
||||
* @param config the HTTP invoker configuration that specifies the target service
|
||||
* @param con the HttpURLConnection to validate
|
||||
* @throws IOException if validation failed
|
||||
* @see java.net.HttpURLConnection#getResponseCode()
|
||||
*/
|
||||
protected void validateResponse(HttpInvokerClientConfiguration config, HttpURLConnection con)
|
||||
throws IOException {
|
||||
|
||||
if (con.getResponseCode() >= 300) {
|
||||
throw new IOException(
|
||||
"Did not receive successful HTTP response: status code = " + con.getResponseCode() +
|
||||
", status message = [" + con.getResponseMessage() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the response body from the given executed remote invocation
|
||||
* request.
|
||||
* <p>The default implementation simply reads the serialized invocation
|
||||
* from the HttpURLConnection's InputStream. If the response is recognized
|
||||
* as GZIP response, the InputStream will get wrapped in a GZIPInputStream.
|
||||
* @param config the HTTP invoker configuration that specifies the target service
|
||||
* @param con the HttpURLConnection to read the response body from
|
||||
* @return an InputStream for the response body
|
||||
* @throws IOException if thrown by I/O methods
|
||||
* @see #isGzipResponse
|
||||
* @see java.util.zip.GZIPInputStream
|
||||
* @see java.net.HttpURLConnection#getInputStream()
|
||||
* @see java.net.HttpURLConnection#getHeaderField(int)
|
||||
* @see java.net.HttpURLConnection#getHeaderFieldKey(int)
|
||||
*/
|
||||
protected InputStream readResponseBody(HttpInvokerClientConfiguration config, HttpURLConnection con)
|
||||
throws IOException {
|
||||
|
||||
if (isGzipResponse(con)) {
|
||||
// GZIP response found - need to unzip.
|
||||
return new GZIPInputStream(con.getInputStream());
|
||||
}
|
||||
else {
|
||||
// Plain response found.
|
||||
return con.getInputStream();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given response is a GZIP response.
|
||||
* <p>Default implementation checks whether the HTTP "Content-Encoding"
|
||||
* header contains "gzip" (in any casing).
|
||||
* @param con the HttpURLConnection to check
|
||||
*/
|
||||
protected boolean isGzipResponse(HttpURLConnection con) {
|
||||
String encodingHeader = con.getHeaderField(HTTP_HEADER_CONTENT_ENCODING);
|
||||
return (encodingHeader != null && encodingHeader.toLowerCase().contains(ENCODING_GZIP));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.httpinvoker;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
|
||||
import org.springframework.remoting.rmi.RemoteInvocationSerializingExporter;
|
||||
import org.springframework.remoting.support.RemoteInvocation;
|
||||
import org.springframework.remoting.support.RemoteInvocationResult;
|
||||
|
||||
/**
|
||||
* HTTP request handler that exports the specified service bean as
|
||||
* HTTP invoker service endpoint, accessible via an HTTP invoker proxy.
|
||||
* Designed for Sun's JRE 1.6 HTTP server, implementing the
|
||||
* {@link com.sun.net.httpserver.HttpHandler} interface.
|
||||
*
|
||||
* <p>Deserializes remote invocation objects and serializes remote invocation
|
||||
* result objects. Uses Java serialization just like RMI, but provides the
|
||||
* same ease of setup as Caucho's HTTP-based Hessian and Burlap protocols.
|
||||
*
|
||||
* <p><b>HTTP invoker is the recommended protocol for Java-to-Java remoting.</b>
|
||||
* It is more powerful and more extensible than Hessian and Burlap, at the
|
||||
* expense of being tied to Java. Nevertheless, it is as easy to set up as
|
||||
* Hessian and Burlap, which is its main advantage compared to RMI.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5.1
|
||||
* @see org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor
|
||||
* @see org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean
|
||||
* @see org.springframework.remoting.caucho.SimpleHessianServiceExporter
|
||||
* @see org.springframework.remoting.caucho.SimpleBurlapServiceExporter
|
||||
*/
|
||||
public class SimpleHttpInvokerServiceExporter extends RemoteInvocationSerializingExporter
|
||||
implements HttpHandler {
|
||||
|
||||
/**
|
||||
* Reads a remote invocation from the request, executes it,
|
||||
* and writes the remote invocation result to the response.
|
||||
* @see #readRemoteInvocation(com.sun.net.httpserver.HttpExchange)
|
||||
* @see #invokeAndCreateResult(org.springframework.remoting.support.RemoteInvocation, Object)
|
||||
* @see #writeRemoteInvocationResult(com.sun.net.httpserver.HttpExchange, org.springframework.remoting.support.RemoteInvocationResult)
|
||||
*/
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
try {
|
||||
RemoteInvocation invocation = readRemoteInvocation(exchange);
|
||||
RemoteInvocationResult result = invokeAndCreateResult(invocation, getProxy());
|
||||
writeRemoteInvocationResult(exchange, result);
|
||||
exchange.close();
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
exchange.sendResponseHeaders(500, -1);
|
||||
logger.error("Class not found during deserialization", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a RemoteInvocation from the given HTTP request.
|
||||
* <p>Delegates to
|
||||
* {@link #readRemoteInvocation(com.sun.net.httpserver.HttpExchange, java.io.InputStream)}
|
||||
* with the
|
||||
* {@link com.sun.net.httpserver.HttpExchange#getRequestBody()} request's input stream}.
|
||||
* @param exchange current HTTP request/response
|
||||
* @return the RemoteInvocation object
|
||||
* @throws java.io.IOException in case of I/O failure
|
||||
* @throws ClassNotFoundException if thrown by deserialization
|
||||
*/
|
||||
protected RemoteInvocation readRemoteInvocation(HttpExchange exchange)
|
||||
throws IOException, ClassNotFoundException {
|
||||
|
||||
return readRemoteInvocation(exchange, exchange.getRequestBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a RemoteInvocation object from the given InputStream.
|
||||
* <p>Gives {@link #decorateInputStream} a chance to decorate the stream
|
||||
* first (for example, for custom encryption or compression). Creates a
|
||||
* {@link org.springframework.remoting.rmi.CodebaseAwareObjectInputStream}
|
||||
* and calls {@link #doReadRemoteInvocation} to actually read the object.
|
||||
* <p>Can be overridden for custom serialization of the invocation.
|
||||
* @param exchange current HTTP request/response
|
||||
* @param is the InputStream to read from
|
||||
* @return the RemoteInvocation object
|
||||
* @throws java.io.IOException in case of I/O failure
|
||||
* @throws ClassNotFoundException if thrown during deserialization
|
||||
*/
|
||||
protected RemoteInvocation readRemoteInvocation(HttpExchange exchange, InputStream is)
|
||||
throws IOException, ClassNotFoundException {
|
||||
|
||||
ObjectInputStream ois = createObjectInputStream(decorateInputStream(exchange, is));
|
||||
return doReadRemoteInvocation(ois);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the InputStream to use for reading remote invocations,
|
||||
* potentially decorating the given original InputStream.
|
||||
* <p>The default implementation returns the given stream as-is.
|
||||
* Can be overridden, for example, for custom encryption or compression.
|
||||
* @param exchange current HTTP request/response
|
||||
* @param is the original InputStream
|
||||
* @return the potentially decorated InputStream
|
||||
* @throws java.io.IOException in case of I/O failure
|
||||
*/
|
||||
protected InputStream decorateInputStream(HttpExchange exchange, InputStream is) throws IOException {
|
||||
return is;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given RemoteInvocationResult to the given HTTP response.
|
||||
* @param exchange current HTTP request/response
|
||||
* @param result the RemoteInvocationResult object
|
||||
* @throws java.io.IOException in case of I/O failure
|
||||
*/
|
||||
protected void writeRemoteInvocationResult(HttpExchange exchange, RemoteInvocationResult result)
|
||||
throws IOException {
|
||||
|
||||
exchange.getResponseHeaders().set("Content-Type", getContentType());
|
||||
exchange.sendResponseHeaders(200, 0);
|
||||
writeRemoteInvocationResult(exchange, result, exchange.getResponseBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the given RemoteInvocation to the given OutputStream.
|
||||
* <p>The default implementation gives {@link #decorateOutputStream} a chance
|
||||
* to decorate the stream first (for example, for custom encryption or compression).
|
||||
* Creates an {@link java.io.ObjectOutputStream} for the final stream and calls
|
||||
* {@link #doWriteRemoteInvocationResult} to actually write the object.
|
||||
* <p>Can be overridden for custom serialization of the invocation.
|
||||
* @param exchange current HTTP request/response
|
||||
* @param result the RemoteInvocationResult object
|
||||
* @param os the OutputStream to write to
|
||||
* @throws java.io.IOException in case of I/O failure
|
||||
* @see #decorateOutputStream
|
||||
* @see #doWriteRemoteInvocationResult
|
||||
*/
|
||||
protected void writeRemoteInvocationResult(
|
||||
HttpExchange exchange, RemoteInvocationResult result, OutputStream os) throws IOException {
|
||||
|
||||
ObjectOutputStream oos = createObjectOutputStream(decorateOutputStream(exchange, os));
|
||||
doWriteRemoteInvocationResult(result, oos);
|
||||
oos.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the OutputStream to use for writing remote invocation results,
|
||||
* potentially decorating the given original OutputStream.
|
||||
* <p>The default implementation returns the given stream as-is.
|
||||
* Can be overridden, for example, for custom encryption or compression.
|
||||
* @param exchange current HTTP request/response
|
||||
* @param os the original OutputStream
|
||||
* @return the potentially decorated OutputStream
|
||||
* @throws java.io.IOException in case of I/O failure
|
||||
*/
|
||||
protected OutputStream decorateOutputStream(HttpExchange exchange, OutputStream os) throws IOException {
|
||||
return os;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
/**
|
||||
*
|
||||
* Remoting classes for transparent Java-to-Java remoting via HTTP invokers.
|
||||
* Uses Java serialization just like RMI, but provides the same ease of setup
|
||||
* as Caucho's HTTP-based Hessian and Burlap protocols.
|
||||
*
|
||||
* <p><b>HTTP invoker is the recommended protocol for Java-to-Java remoting.</b>
|
||||
* It is more powerful and more extensible than Hessian and Burlap, at the
|
||||
* expense of being tied to Java. Neverthelesss, it is as easy to set up as
|
||||
* Hessian and Burlap, which is its main advantage compared to RMI.
|
||||
*
|
||||
*/
|
||||
package org.springframework.remoting.httpinvoker;
|
||||
|
||||
@@ -0,0 +1,749 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.jaxrpc;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.rmi.Remote;
|
||||
import java.rmi.RemoteException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import javax.xml.namespace.QName;
|
||||
import javax.xml.rpc.Call;
|
||||
import javax.xml.rpc.JAXRPCException;
|
||||
import javax.xml.rpc.Service;
|
||||
import javax.xml.rpc.ServiceException;
|
||||
import javax.xml.rpc.Stub;
|
||||
import javax.xml.rpc.soap.SOAPFaultException;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.remoting.RemoteLookupFailureException;
|
||||
import org.springframework.remoting.RemoteProxyFailureException;
|
||||
import org.springframework.remoting.rmi.RmiClientInterceptorUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link org.aopalliance.intercept.MethodInterceptor} for accessing a specific port
|
||||
* of a JAX-RPC service. Uses either {@link LocalJaxRpcServiceFactory}'s facilities
|
||||
* underneath or takes an explicit reference to an existing JAX-RPC Service instance
|
||||
* (e.g. obtained via a {@link org.springframework.jndi.JndiObjectFactoryBean}).
|
||||
*
|
||||
* <p>Allows to set JAX-RPC's standard stub properties directly, via the
|
||||
* "username", "password", "endpointAddress" and "maintainSession" properties.
|
||||
* For typical usage, it is not necessary to specify those.
|
||||
*
|
||||
* <p>In standard JAX-RPC style, this invoker is used with an RMI service interface.
|
||||
* Alternatively, this invoker can also proxy a JAX-RPC service with a matching
|
||||
* non-RMI business interface, that is, an interface that declares the service methods
|
||||
* without RemoteExceptions. In the latter case, RemoteExceptions thrown by JAX-RPC
|
||||
* will automatically get converted to Spring's unchecked RemoteAccessException.
|
||||
*
|
||||
* <p>Setting "serviceInterface" is usually sufficient: The invoker will automatically
|
||||
* use JAX-RPC "dynamic invocations" via the Call API in this case, no matter whether
|
||||
* the specified interface is an RMI or non-RMI interface. Alternatively, a corresponding
|
||||
* JAX-RPC port interface can be specified as "portInterface", which will turn this
|
||||
* invoker into "static invocation" mode (operating on a standard JAX-RPC port stub).
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 15.12.2003
|
||||
* @see #setPortName
|
||||
* @see #setServiceInterface
|
||||
* @see #setPortInterface
|
||||
* @see javax.xml.rpc.Service#createCall
|
||||
* @see javax.xml.rpc.Service#getPort
|
||||
* @see org.springframework.remoting.RemoteAccessException
|
||||
* @see org.springframework.jndi.JndiObjectFactoryBean
|
||||
* @deprecated in favor of JAX-WS support in <code>org.springframework.remoting.jaxws</code>
|
||||
*/
|
||||
@Deprecated
|
||||
public class JaxRpcPortClientInterceptor extends LocalJaxRpcServiceFactory
|
||||
implements MethodInterceptor, InitializingBean {
|
||||
|
||||
private Service jaxRpcService;
|
||||
|
||||
private Service serviceToUse;
|
||||
|
||||
private String portName;
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
private String endpointAddress;
|
||||
|
||||
private boolean maintainSession;
|
||||
|
||||
/** Map of custom properties, keyed by property name (String) */
|
||||
private final Map<String, Object> customPropertyMap = new HashMap<String, Object>();
|
||||
|
||||
private Class serviceInterface;
|
||||
|
||||
private Class portInterface;
|
||||
|
||||
private boolean lookupServiceOnStartup = true;
|
||||
|
||||
private boolean refreshServiceAfterConnectFailure = false;
|
||||
|
||||
private QName portQName;
|
||||
|
||||
private Remote portStub;
|
||||
|
||||
private final Object preparationMonitor = new Object();
|
||||
|
||||
|
||||
/**
|
||||
* Set a reference to an existing JAX-RPC Service instance,
|
||||
* for example obtained via {@link org.springframework.jndi.JndiObjectFactoryBean}.
|
||||
* If not set, {@link LocalJaxRpcServiceFactory}'s properties have to be specified.
|
||||
* @see #setServiceFactoryClass
|
||||
* @see #setWsdlDocumentUrl
|
||||
* @see #setNamespaceUri
|
||||
* @see #setServiceName
|
||||
* @see org.springframework.jndi.JndiObjectFactoryBean
|
||||
*/
|
||||
public void setJaxRpcService(Service jaxRpcService) {
|
||||
this.jaxRpcService = jaxRpcService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a reference to an existing JAX-RPC Service instance, if any.
|
||||
*/
|
||||
public Service getJaxRpcService() {
|
||||
return this.jaxRpcService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the port.
|
||||
* Corresponds to the "wsdl:port" name.
|
||||
*/
|
||||
public void setPortName(String portName) {
|
||||
this.portName = portName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the port.
|
||||
*/
|
||||
public String getPortName() {
|
||||
return this.portName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the username to specify on the stub or call.
|
||||
* @see javax.xml.rpc.Stub#USERNAME_PROPERTY
|
||||
* @see javax.xml.rpc.Call#USERNAME_PROPERTY
|
||||
*/
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the username to specify on the stub or call.
|
||||
*/
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the password to specify on the stub or call.
|
||||
* @see javax.xml.rpc.Stub#PASSWORD_PROPERTY
|
||||
* @see javax.xml.rpc.Call#PASSWORD_PROPERTY
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the password to specify on the stub or call.
|
||||
*/
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the endpoint address to specify on the stub or call.
|
||||
* @see javax.xml.rpc.Stub#ENDPOINT_ADDRESS_PROPERTY
|
||||
* @see javax.xml.rpc.Call#setTargetEndpointAddress
|
||||
*/
|
||||
public void setEndpointAddress(String endpointAddress) {
|
||||
this.endpointAddress = endpointAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the endpoint address to specify on the stub or call.
|
||||
*/
|
||||
public String getEndpointAddress() {
|
||||
return this.endpointAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maintain session flag to specify on the stub or call.
|
||||
* @see javax.xml.rpc.Stub#SESSION_MAINTAIN_PROPERTY
|
||||
* @see javax.xml.rpc.Call#SESSION_MAINTAIN_PROPERTY
|
||||
*/
|
||||
public void setMaintainSession(boolean maintainSession) {
|
||||
this.maintainSession = maintainSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maintain session flag to specify on the stub or call.
|
||||
*/
|
||||
public boolean isMaintainSession() {
|
||||
return this.maintainSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom properties to be set on the stub or call.
|
||||
* <p>Can be populated with a String "value" (parsed via PropertiesEditor)
|
||||
* or a "props" element in XML bean definitions.
|
||||
* @see javax.xml.rpc.Stub#_setProperty
|
||||
* @see javax.xml.rpc.Call#setProperty
|
||||
*/
|
||||
public void setCustomProperties(Properties customProperties) {
|
||||
CollectionUtils.mergePropertiesIntoMap(customProperties, this.customPropertyMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom properties to be set on the stub or call.
|
||||
* <p>Can be populated with a "map" or "props" element in XML bean definitions.
|
||||
* @see javax.xml.rpc.Stub#_setProperty
|
||||
* @see javax.xml.rpc.Call#setProperty
|
||||
*/
|
||||
public void setCustomPropertyMap(Map<String, Object> customProperties) {
|
||||
if (customProperties != null) {
|
||||
for (Map.Entry<String, Object> entry : customProperties.entrySet()) {
|
||||
addCustomProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow Map access to the custom properties to be set on the stub
|
||||
* or call, with the option to add or override specific entries.
|
||||
* <p>Useful for specifying entries directly, for example via
|
||||
* "customPropertyMap[myKey]". This is particularly useful for
|
||||
* adding or overriding entries in child bean definitions.
|
||||
*/
|
||||
public Map<String, Object> getCustomPropertyMap() {
|
||||
return this.customPropertyMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom property to this JAX-RPC Stub/Call.
|
||||
* @param name the name of the attribute to expose
|
||||
* @param value the attribute value to expose
|
||||
* @see javax.xml.rpc.Stub#_setProperty
|
||||
* @see javax.xml.rpc.Call#setProperty
|
||||
*/
|
||||
public void addCustomProperty(String name, Object value) {
|
||||
this.customPropertyMap.put(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the interface of the service that this factory should create a proxy for.
|
||||
* This will typically be a non-RMI business interface, although you can also
|
||||
* use an RMI port interface as recommended by JAX-RPC here.
|
||||
* <p>Calls on the specified service interface will either be translated to the
|
||||
* underlying RMI port interface (in case of a "portInterface" being specified)
|
||||
* or to dynamic calls (using the JAX-RPC Dynamic Invocation Interface).
|
||||
* <p>The dynamic call mechanism has the advantage that you don't need to
|
||||
* maintain an RMI port interface in addition to an existing non-RMI business
|
||||
* interface. In terms of configuration, specifying the business interface
|
||||
* as "serviceInterface" will be enough; this interceptor will automatically
|
||||
* use dynamic calls in such a scenario.
|
||||
* @see javax.xml.rpc.Service#createCall
|
||||
* @see #setPortInterface
|
||||
*/
|
||||
public void setServiceInterface(Class serviceInterface) {
|
||||
if (serviceInterface != null && !serviceInterface.isInterface()) {
|
||||
throw new IllegalArgumentException("'serviceInterface' must be an interface");
|
||||
}
|
||||
this.serviceInterface = serviceInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the interface of the service that this factory should create a proxy for.
|
||||
*/
|
||||
public Class getServiceInterface() {
|
||||
return this.serviceInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the JAX-RPC port interface to use. Only needs to be set if a JAX-RPC
|
||||
* port stub should be used instead of the dynamic call mechanism.
|
||||
* See the javadoc of the "serviceInterface" property for more details.
|
||||
* <p>The interface must be suitable for a JAX-RPC port, that is, it must be
|
||||
* an RMI service interface (that extends <code>java.rmi.Remote</code>).
|
||||
* <p><b>NOTE:</b> Check whether your JAX-RPC provider returns thread-safe
|
||||
* port stubs. If not, use the dynamic call mechanism instead, which will
|
||||
* always be thread-safe. In particular, do not use JAX-RPC port stubs
|
||||
* with Apache Axis, whose port stubs are known to be non-thread-safe.
|
||||
* @see javax.xml.rpc.Service#getPort
|
||||
* @see java.rmi.Remote
|
||||
* @see #setServiceInterface
|
||||
*/
|
||||
public void setPortInterface(Class portInterface) {
|
||||
if (portInterface != null &&
|
||||
(!portInterface.isInterface() || !Remote.class.isAssignableFrom(portInterface))) {
|
||||
throw new IllegalArgumentException(
|
||||
"'portInterface' must be an interface derived from [java.rmi.Remote]");
|
||||
}
|
||||
this.portInterface = portInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JAX-RPC port interface to use.
|
||||
*/
|
||||
public Class getPortInterface() {
|
||||
return this.portInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to look up the JAX-RPC service on startup.
|
||||
* <p>Default is "true". Turn this flag off to allow for late start
|
||||
* of the target server. In this case, the JAX-RPC service will be
|
||||
* lazily fetched on first access.
|
||||
*/
|
||||
public void setLookupServiceOnStartup(boolean lookupServiceOnStartup) {
|
||||
this.lookupServiceOnStartup = lookupServiceOnStartup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to refresh the JAX-RPC service on connect failure,
|
||||
* that is, whenever a JAX-RPC invocation throws a RemoteException.
|
||||
* <p>Default is "false", keeping a reference to the JAX-RPC service
|
||||
* in any case, retrying the next invocation on the same service
|
||||
* even in case of failure. Turn this flag on to reinitialize the
|
||||
* entire service in case of connect failures.
|
||||
*/
|
||||
public void setRefreshServiceAfterConnectFailure(boolean refreshServiceAfterConnectFailure) {
|
||||
this.refreshServiceAfterConnectFailure = refreshServiceAfterConnectFailure;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepares the JAX-RPC service and port if the "lookupServiceOnStartup"
|
||||
* is turned on (which it is by default).
|
||||
*/
|
||||
public void afterPropertiesSet() {
|
||||
if (this.lookupServiceOnStartup) {
|
||||
prepare();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and initialize the JAX-RPC service for the specified port.
|
||||
* <p>Prepares a JAX-RPC stub if possible (if an RMI interface is available);
|
||||
* falls back to JAX-RPC dynamic calls else. Using dynamic calls can be enforced
|
||||
* through overriding {@link #alwaysUseJaxRpcCall} to return <code>true</code>.
|
||||
* <p>{@link #postProcessJaxRpcService} and {@link #postProcessPortStub}
|
||||
* hooks are available for customization in subclasses. When using dynamic calls,
|
||||
* each can be post-processed via {@link #postProcessJaxRpcCall}.
|
||||
* @throws RemoteLookupFailureException if service initialization or port stub creation failed
|
||||
*/
|
||||
public void prepare() throws RemoteLookupFailureException {
|
||||
if (getPortName() == null) {
|
||||
throw new IllegalArgumentException("Property 'portName' is required");
|
||||
}
|
||||
|
||||
synchronized (this.preparationMonitor) {
|
||||
this.serviceToUse = null;
|
||||
|
||||
// Cache the QName for the port.
|
||||
this.portQName = getQName(getPortName());
|
||||
|
||||
try {
|
||||
Service service = getJaxRpcService();
|
||||
if (service == null) {
|
||||
service = createJaxRpcService();
|
||||
}
|
||||
else {
|
||||
postProcessJaxRpcService(service);
|
||||
}
|
||||
|
||||
Class portInterface = getPortInterface();
|
||||
if (portInterface != null && !alwaysUseJaxRpcCall()) {
|
||||
// JAX-RPC-compliant port interface -> using JAX-RPC stub for port.
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Creating JAX-RPC proxy for JAX-RPC port [" + this.portQName +
|
||||
"], using port interface [" + portInterface.getName() + "]");
|
||||
}
|
||||
Remote remoteObj = service.getPort(this.portQName, portInterface);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
Class serviceInterface = getServiceInterface();
|
||||
if (serviceInterface != null) {
|
||||
boolean isImpl = serviceInterface.isInstance(remoteObj);
|
||||
logger.debug("Using service interface [" + serviceInterface.getName() + "] for JAX-RPC port [" +
|
||||
this.portQName + "] - " + (!isImpl ? "not" : "") + " directly implemented");
|
||||
}
|
||||
}
|
||||
|
||||
if (!(remoteObj instanceof Stub)) {
|
||||
throw new RemoteLookupFailureException("Port stub of class [" + remoteObj.getClass().getName() +
|
||||
"] is not a valid JAX-RPC stub: it does not implement interface [javax.xml.rpc.Stub]");
|
||||
}
|
||||
Stub stub = (Stub) remoteObj;
|
||||
|
||||
// Apply properties to JAX-RPC stub.
|
||||
preparePortStub(stub);
|
||||
|
||||
// Allow for custom post-processing in subclasses.
|
||||
postProcessPortStub(stub);
|
||||
|
||||
this.portStub = remoteObj;
|
||||
}
|
||||
|
||||
else {
|
||||
// No JAX-RPC-compliant port interface -> using JAX-RPC dynamic calls.
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Using JAX-RPC dynamic calls for JAX-RPC port [" + this.portQName + "]");
|
||||
}
|
||||
}
|
||||
|
||||
this.serviceToUse = service;
|
||||
}
|
||||
catch (ServiceException ex) {
|
||||
throw new RemoteLookupFailureException(
|
||||
"Failed to initialize service for JAX-RPC port [" + this.portQName + "]", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to always use JAX-RPC dynamic calls.
|
||||
* Called by <code>afterPropertiesSet</code>.
|
||||
* <p>Default is "false"; if an RMI interface is specified as "portInterface"
|
||||
* or "serviceInterface", it will be used to create a JAX-RPC port stub.
|
||||
* <p>Can be overridden to enforce the use of the JAX-RPC Call API,
|
||||
* for example if there is a need to customize at the Call level.
|
||||
* This just necessary if you you want to use an RMI interface as
|
||||
* "serviceInterface", though; in case of only a non-RMI interface being
|
||||
* available, this interceptor will fall back to the Call API anyway.
|
||||
* @see #postProcessJaxRpcCall
|
||||
*/
|
||||
protected boolean alwaysUseJaxRpcCall() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the prepared service of this interceptor,
|
||||
* allowing for reinitialization on next access.
|
||||
*/
|
||||
protected void reset() {
|
||||
synchronized (this.preparationMonitor) {
|
||||
this.serviceToUse = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this client interceptor has already been prepared,
|
||||
* i.e. has already looked up the JAX-RPC service and port.
|
||||
*/
|
||||
protected boolean isPrepared() {
|
||||
synchronized (this.preparationMonitor) {
|
||||
return (this.serviceToUse != null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the prepared QName for the port.
|
||||
* @see #setPortName
|
||||
* @see #getQName
|
||||
*/
|
||||
protected final QName getPortQName() {
|
||||
return this.portQName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare the given JAX-RPC port stub, applying properties to it.
|
||||
* Called by {@link #prepare}.
|
||||
* <p>Just applied when actually creating a JAX-RPC port stub, in case of a
|
||||
* compliant port interface. Else, JAX-RPC dynamic calls will be used.
|
||||
* @param stub the current JAX-RPC port stub
|
||||
* @see #setUsername
|
||||
* @see #setPassword
|
||||
* @see #setEndpointAddress
|
||||
* @see #setMaintainSession
|
||||
* @see #setCustomProperties
|
||||
* @see #setPortInterface
|
||||
* @see #prepareJaxRpcCall
|
||||
*/
|
||||
protected void preparePortStub(Stub stub) {
|
||||
String username = getUsername();
|
||||
if (username != null) {
|
||||
stub._setProperty(Stub.USERNAME_PROPERTY, username);
|
||||
}
|
||||
String password = getPassword();
|
||||
if (password != null) {
|
||||
stub._setProperty(Stub.PASSWORD_PROPERTY, password);
|
||||
}
|
||||
String endpointAddress = getEndpointAddress();
|
||||
if (endpointAddress != null) {
|
||||
stub._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, endpointAddress);
|
||||
}
|
||||
if (isMaintainSession()) {
|
||||
stub._setProperty(Stub.SESSION_MAINTAIN_PROPERTY, Boolean.TRUE);
|
||||
}
|
||||
if (this.customPropertyMap != null) {
|
||||
for (Map.Entry<String, Object> entry : this.customPropertyMap.entrySet()) {
|
||||
stub._setProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-process the given JAX-RPC port stub. Called by {@link #prepare}.
|
||||
* <p>The default implementation is empty.
|
||||
* <p>Just applied when actually creating a JAX-RPC port stub, in case of a
|
||||
* compliant port interface. Else, JAX-RPC dynamic calls will be used.
|
||||
* @param stub the current JAX-RPC port stub
|
||||
* (can be cast to an implementation-specific class if necessary)
|
||||
* @see #setPortInterface
|
||||
* @see #postProcessJaxRpcCall
|
||||
*/
|
||||
protected void postProcessPortStub(Stub stub) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying JAX-RPC port stub that this interceptor delegates to
|
||||
* for each method invocation on the proxy.
|
||||
*/
|
||||
protected Remote getPortStub() {
|
||||
return this.portStub;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translates the method invocation into a JAX-RPC service invocation.
|
||||
* <p>Prepares the service on the fly, if necessary, in case of lazy
|
||||
* lookup or a connect failure having happened.
|
||||
* @see #prepare()
|
||||
* @see #doInvoke
|
||||
*/
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
if (AopUtils.isToStringMethod(invocation.getMethod())) {
|
||||
return "JAX-RPC proxy for port [" + getPortName() + "] of service [" + getServiceName() + "]";
|
||||
}
|
||||
// Lazily prepare service and stub if necessary.
|
||||
synchronized (this.preparationMonitor) {
|
||||
if (!isPrepared()) {
|
||||
prepare();
|
||||
}
|
||||
}
|
||||
return doInvoke(invocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a JAX-RPC service invocation based on the given method invocation.
|
||||
* <p>Uses traditional RMI stub invocation if a JAX-RPC port stub is available;
|
||||
* falls back to JAX-RPC dynamic calls else.
|
||||
* @param invocation the AOP method invocation
|
||||
* @return the invocation result, if any
|
||||
* @throws Throwable in case of invocation failure
|
||||
* @see #getPortStub()
|
||||
* @see #doInvoke(org.aopalliance.intercept.MethodInvocation, java.rmi.Remote)
|
||||
* @see #performJaxRpcCall(org.aopalliance.intercept.MethodInvocation, javax.xml.rpc.Service)
|
||||
*/
|
||||
protected Object doInvoke(MethodInvocation invocation) throws Throwable {
|
||||
Remote stub = getPortStub();
|
||||
try {
|
||||
if (stub != null) {
|
||||
// JAX-RPC port stub available -> traditional RMI stub invocation.
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Invoking operation '" + invocation.getMethod().getName() + "' on JAX-RPC port stub");
|
||||
}
|
||||
return doInvoke(invocation, stub);
|
||||
}
|
||||
else {
|
||||
// No JAX-RPC stub -> using JAX-RPC dynamic calls.
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Invoking operation '" + invocation.getMethod().getName() + "' as JAX-RPC dynamic call");
|
||||
}
|
||||
return performJaxRpcCall(invocation, this.serviceToUse);
|
||||
}
|
||||
}
|
||||
catch (RemoteException ex) {
|
||||
throw handleRemoteException(invocation.getMethod(), ex);
|
||||
}
|
||||
catch (SOAPFaultException ex) {
|
||||
throw new JaxRpcSoapFaultException(ex);
|
||||
}
|
||||
catch (JAXRPCException ex) {
|
||||
throw new RemoteProxyFailureException("Invalid JAX-RPC call configuration", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a JAX-RPC service invocation on the given port stub.
|
||||
* @param invocation the AOP method invocation
|
||||
* @param portStub the RMI port stub to invoke
|
||||
* @return the invocation result, if any
|
||||
* @throws Throwable in case of invocation failure
|
||||
* @see #getPortStub()
|
||||
* @see #doInvoke(org.aopalliance.intercept.MethodInvocation, java.rmi.Remote)
|
||||
* @see #performJaxRpcCall
|
||||
*/
|
||||
protected Object doInvoke(MethodInvocation invocation, Remote portStub) throws Throwable {
|
||||
try {
|
||||
return RmiClientInterceptorUtils.invokeRemoteMethod(invocation, portStub);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw ex.getTargetException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a JAX-RPC dynamic call for the given AOP method invocation.
|
||||
* Delegates to {@link #prepareJaxRpcCall} and
|
||||
* {@link #postProcessJaxRpcCall} for setting up the call object.
|
||||
* <p>The default implementation uses method name as JAX-RPC operation name
|
||||
* and method arguments as arguments for the JAX-RPC call. Can be
|
||||
* overridden in subclasses for custom operation names and/or arguments.
|
||||
* @param invocation the current AOP MethodInvocation that should
|
||||
* be converted to a JAX-RPC call
|
||||
* @param service the JAX-RPC Service to use for the call
|
||||
* @return the return value of the invocation, if any
|
||||
* @throws Throwable the exception thrown by the invocation, if any
|
||||
* @see #prepareJaxRpcCall
|
||||
* @see #postProcessJaxRpcCall
|
||||
*/
|
||||
protected Object performJaxRpcCall(MethodInvocation invocation, Service service) throws Throwable {
|
||||
Method method = invocation.getMethod();
|
||||
QName portQName = this.portQName;
|
||||
|
||||
// Create JAX-RPC call object, using the method name as operation name.
|
||||
// Synchronized because of non-thread-safe Axis implementation!
|
||||
Call call = null;
|
||||
synchronized (service) {
|
||||
call = service.createCall(portQName, method.getName());
|
||||
}
|
||||
|
||||
// Apply properties to JAX-RPC stub.
|
||||
prepareJaxRpcCall(call);
|
||||
|
||||
// Allow for custom post-processing in subclasses.
|
||||
postProcessJaxRpcCall(call, invocation);
|
||||
|
||||
// Perform actual invocation.
|
||||
return call.invoke(invocation.getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the given JAX-RPC call, applying properties to it. Called by {@link #invoke}.
|
||||
* <p>Just applied when actually using JAX-RPC dynamic calls, i.e. if no compliant
|
||||
* port interface was specified. Else, a JAX-RPC port stub will be used.
|
||||
* @param call the current JAX-RPC call object
|
||||
* @see #setUsername
|
||||
* @see #setPassword
|
||||
* @see #setEndpointAddress
|
||||
* @see #setMaintainSession
|
||||
* @see #setCustomProperties
|
||||
* @see #setPortInterface
|
||||
* @see #preparePortStub
|
||||
*/
|
||||
protected void prepareJaxRpcCall(Call call) {
|
||||
String username = getUsername();
|
||||
if (username != null) {
|
||||
call.setProperty(Call.USERNAME_PROPERTY, username);
|
||||
}
|
||||
String password = getPassword();
|
||||
if (password != null) {
|
||||
call.setProperty(Call.PASSWORD_PROPERTY, password);
|
||||
}
|
||||
String endpointAddress = getEndpointAddress();
|
||||
if (endpointAddress != null) {
|
||||
call.setTargetEndpointAddress(endpointAddress);
|
||||
}
|
||||
if (isMaintainSession()) {
|
||||
call.setProperty(Call.SESSION_MAINTAIN_PROPERTY, Boolean.TRUE);
|
||||
}
|
||||
if (this.customPropertyMap != null) {
|
||||
for (Map.Entry<String, Object> entry : this.customPropertyMap.entrySet()) {
|
||||
call.setProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-process the given JAX-RPC call. Called by {@link #invoke}.
|
||||
* <p>The default implementation is empty.
|
||||
* <p>Just applied when actually using JAX-RPC dynamic calls, i.e. if no compliant
|
||||
* port interface was specified. Else, a JAX-RPC port stub will be used.
|
||||
* @param call the current JAX-RPC call object
|
||||
* (can be cast to an implementation-specific class if necessary)
|
||||
* @param invocation the current AOP MethodInvocation that the call was
|
||||
* created for (can be used to check method name, method parameters
|
||||
* and/or passed-in arguments)
|
||||
* @see #setPortInterface
|
||||
* @see #postProcessPortStub
|
||||
*/
|
||||
protected void postProcessJaxRpcCall(Call call, MethodInvocation invocation) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the given RemoteException that was thrown from a JAX-RPC port stub
|
||||
* or JAX-RPC call invocation.
|
||||
* @param method the service interface method that we invoked
|
||||
* @param ex the original RemoteException
|
||||
* @return the exception to rethrow (may be the original RemoteException
|
||||
* or an extracted/wrapped exception, but never <code>null</code>)
|
||||
*/
|
||||
protected Throwable handleRemoteException(Method method, RemoteException ex) {
|
||||
boolean isConnectFailure = isConnectFailure(ex);
|
||||
if (isConnectFailure && this.refreshServiceAfterConnectFailure) {
|
||||
reset();
|
||||
}
|
||||
Throwable cause = ex.getCause();
|
||||
if (cause != null && ReflectionUtils.declaresException(method, cause.getClass())) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Rethrowing wrapped exception of type [" + cause.getClass().getName() + "] as-is");
|
||||
}
|
||||
// Declared on the service interface: probably a wrapped business exception.
|
||||
return ex.getCause();
|
||||
}
|
||||
else {
|
||||
// Throw either a RemoteAccessException or the original RemoteException,
|
||||
// depending on what the service interface declares.
|
||||
return RmiClientInterceptorUtils.convertRmiAccessException(
|
||||
method, ex, isConnectFailure, this.portQName.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given RMI exception indicates a connect failure.
|
||||
* <p>The default implementation returns <code>true</code> unless the
|
||||
* exception class name (or exception superclass name) contains the term
|
||||
* "Fault" (e.g. "AxisFault"), assuming that the JAX-RPC provider only
|
||||
* throws RemoteException in case of WSDL faults and connect failures.
|
||||
* @param ex the RMI exception to check
|
||||
* @return whether the exception should be treated as connect failure
|
||||
* @see org.springframework.remoting.rmi.RmiClientInterceptorUtils#isConnectFailure
|
||||
*/
|
||||
protected boolean isConnectFailure(RemoteException ex) {
|
||||
return (!ex.getClass().getName().contains("Fault") &&
|
||||
!ex.getClass().getSuperclass().getName().contains("Fault"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.jaxrpc;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link FactoryBean} for a specific port of a JAX-RPC service.
|
||||
* Exposes a proxy for the port, to be used for bean references.
|
||||
* Inherits configuration properties from {@link JaxRpcPortClientInterceptor}.
|
||||
*
|
||||
* <p>This factory is typically used with an RMI service interface. Alternatively,
|
||||
* this factory can also proxy a JAX-RPC service with a matching non-RMI business
|
||||
* interface, i.e. an interface that mirrors the RMI service methods but does not
|
||||
* declare RemoteExceptions. In the latter case, RemoteExceptions thrown by the
|
||||
* JAX-RPC stub will automatically get converted to Spring's unchecked
|
||||
* RemoteAccessException.
|
||||
*
|
||||
* <p>If exposing the JAX-RPC port interface (i.e. an RMI interface) directly,
|
||||
* setting "serviceInterface" is sufficient. If exposing a non-RMI business
|
||||
* interface, the business interface needs to be set as "serviceInterface",
|
||||
* and the JAX-RPC port interface as "portInterface".
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 15.12.2003
|
||||
* @see #setServiceInterface
|
||||
* @see #setPortInterface
|
||||
* @see LocalJaxRpcServiceFactoryBean
|
||||
* @deprecated in favor of JAX-WS support in <code>org.springframework.remoting.jaxws</code>
|
||||
*/
|
||||
@Deprecated
|
||||
public class JaxRpcPortProxyFactoryBean extends JaxRpcPortClientInterceptor
|
||||
implements FactoryBean<Object>, BeanClassLoaderAware {
|
||||
|
||||
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
|
||||
|
||||
private Object serviceProxy;
|
||||
|
||||
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.beanClassLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (getServiceInterface() == null) {
|
||||
// Use JAX-RPC port interface (a traditional RMI interface)
|
||||
// as service interface if none explicitly specified.
|
||||
if (getPortInterface() != null) {
|
||||
setServiceInterface(getPortInterface());
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Property 'serviceInterface' is required");
|
||||
}
|
||||
}
|
||||
super.afterPropertiesSet();
|
||||
this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(this.beanClassLoader);
|
||||
}
|
||||
|
||||
|
||||
public Object getObject() {
|
||||
return this.serviceProxy;
|
||||
}
|
||||
|
||||
public Class<?> getObjectType() {
|
||||
return getServiceInterface();
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.jaxrpc;
|
||||
|
||||
import javax.xml.rpc.Service;
|
||||
|
||||
/**
|
||||
* Callback interface for post-processing a JAX-RPC Service.
|
||||
*
|
||||
* <p>Implementations can be registered with {@link LocalJaxRpcServiceFactory}
|
||||
* or one of its subclasses: {@link LocalJaxRpcServiceFactoryBean},
|
||||
* {@link JaxRpcPortClientInterceptor}, or {@link JaxRpcPortProxyFactoryBean}.
|
||||
*
|
||||
* <p>Useful, for example, to register custom type mappings. See the
|
||||
* {@link org.springframework.remoting.jaxrpc.support.AxisBeanMappingServicePostProcessor}
|
||||
* class that registers Axis-specific bean mappings for specified bean classes.
|
||||
* This is defined for the domain objects in the JPetStore same application,
|
||||
* for example.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1.4
|
||||
* @see LocalJaxRpcServiceFactory#setServicePostProcessors
|
||||
* @see LocalJaxRpcServiceFactoryBean#setServicePostProcessors
|
||||
* @see JaxRpcPortClientInterceptor#setServicePostProcessors
|
||||
* @see JaxRpcPortProxyFactoryBean#setServicePostProcessors
|
||||
* @see javax.xml.rpc.Service#getTypeMappingRegistry
|
||||
* @deprecated in favor of JAX-WS support in <code>org.springframework.remoting.jaxws</code>
|
||||
*/
|
||||
@Deprecated
|
||||
public interface JaxRpcServicePostProcessor {
|
||||
|
||||
/**
|
||||
* Post-process the given JAX-RPC {@link Service}.
|
||||
* @param service the current JAX-RPC <code>Service</code>
|
||||
* (can be cast to an implementation-specific class if necessary)
|
||||
*/
|
||||
void postProcessJaxRpcService(Service service);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.jaxrpc;
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
import javax.xml.rpc.soap.SOAPFaultException;
|
||||
|
||||
import org.springframework.remoting.soap.SoapFaultException;
|
||||
|
||||
/**
|
||||
* Spring SoapFaultException adapter for the JAX-RPC
|
||||
* {@link javax.xml.rpc.soap.SOAPFaultException} class.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5
|
||||
* @deprecated in favor of JAX-WS support in <code>org.springframework.remoting.jaxws</code>
|
||||
*/
|
||||
@Deprecated
|
||||
public class JaxRpcSoapFaultException extends SoapFaultException {
|
||||
|
||||
/**
|
||||
* Constructor for JaxRpcSoapFaultException.
|
||||
* @param original the original JAX-RPC SOAPFaultException to wrap
|
||||
*/
|
||||
public JaxRpcSoapFaultException(SOAPFaultException original) {
|
||||
super(original.getMessage(), original);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the wrapped JAX-RPC SOAPFaultException.
|
||||
*/
|
||||
public final SOAPFaultException getOriginalException() {
|
||||
return (SOAPFaultException) getCause();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getFaultCode() {
|
||||
return getOriginalException().getFaultCode().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QName getFaultCodeAsQName() {
|
||||
return getOriginalException().getFaultCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFaultString() {
|
||||
return getOriginalException().getFaultString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFaultActor() {
|
||||
return getOriginalException().getFaultActor();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.jaxrpc;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
import javax.xml.rpc.Service;
|
||||
import javax.xml.rpc.ServiceException;
|
||||
import javax.xml.rpc.ServiceFactory;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
/**
|
||||
* Factory for locally defined JAX-RPC {@link javax.xml.rpc.Service} references.
|
||||
* Uses a JAX-RPC {@link javax.xml.rpc.ServiceFactory} underneath.
|
||||
*
|
||||
* <p>Serves as base class for {@link LocalJaxRpcServiceFactoryBean} as well as
|
||||
* {@link JaxRpcPortClientInterceptor} and {@link JaxRpcPortProxyFactoryBean}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 15.12.2003
|
||||
* @see javax.xml.rpc.ServiceFactory
|
||||
* @see javax.xml.rpc.Service
|
||||
* @see LocalJaxRpcServiceFactoryBean
|
||||
* @see JaxRpcPortClientInterceptor
|
||||
* @see JaxRpcPortProxyFactoryBean
|
||||
* @deprecated in favor of JAX-WS support in <code>org.springframework.remoting.jaxws</code>
|
||||
*/
|
||||
@Deprecated
|
||||
public class LocalJaxRpcServiceFactory {
|
||||
|
||||
/** Logger available to subclasses */
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private ServiceFactory serviceFactory;
|
||||
|
||||
private Class serviceFactoryClass;
|
||||
|
||||
private URL wsdlDocumentUrl;
|
||||
|
||||
private String namespaceUri;
|
||||
|
||||
private String serviceName;
|
||||
|
||||
private Class jaxRpcServiceInterface;
|
||||
|
||||
private Properties jaxRpcServiceProperties;
|
||||
|
||||
private JaxRpcServicePostProcessor[] servicePostProcessors;
|
||||
|
||||
|
||||
/**
|
||||
* Set the ServiceFactory instance to use.
|
||||
* <p>This is an alternative to the common "serviceFactoryClass" property,
|
||||
* allowing for a pre-initialized ServiceFactory instance to be specified.
|
||||
* @see #setServiceFactoryClass
|
||||
*/
|
||||
public void setServiceFactory(ServiceFactory serviceFactory) {
|
||||
this.serviceFactory = serviceFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the specified ServiceFactory instance, if any.
|
||||
*/
|
||||
public ServiceFactory getServiceFactory() {
|
||||
return this.serviceFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ServiceFactory class to use, for example
|
||||
* "org.apache.axis.client.ServiceFactory".
|
||||
* <p>Does not need to be set if the JAX-RPC implementation has registered
|
||||
* itself with the JAX-RPC system property "SERVICEFACTORY_PROPERTY".
|
||||
* @see javax.xml.rpc.ServiceFactory
|
||||
*/
|
||||
public void setServiceFactoryClass(Class serviceFactoryClass) {
|
||||
if (serviceFactoryClass != null && !ServiceFactory.class.isAssignableFrom(serviceFactoryClass)) {
|
||||
throw new IllegalArgumentException("'serviceFactoryClass' must implement [javax.xml.rpc.ServiceFactory]");
|
||||
}
|
||||
this.serviceFactoryClass = serviceFactoryClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ServiceFactory class to use, or <code>null</code> if default.
|
||||
*/
|
||||
public Class getServiceFactoryClass() {
|
||||
return this.serviceFactoryClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URL of the WSDL document that describes the service.
|
||||
*/
|
||||
public void setWsdlDocumentUrl(URL wsdlDocumentUrl) {
|
||||
this.wsdlDocumentUrl = wsdlDocumentUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the URL of the WSDL document that describes the service.
|
||||
*/
|
||||
public URL getWsdlDocumentUrl() {
|
||||
return this.wsdlDocumentUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the namespace URI of the service.
|
||||
* Corresponds to the WSDL "targetNamespace".
|
||||
*/
|
||||
public void setNamespaceUri(String namespaceUri) {
|
||||
this.namespaceUri = (namespaceUri != null ? namespaceUri.trim() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the namespace URI of the service.
|
||||
*/
|
||||
public String getNamespaceUri() {
|
||||
return this.namespaceUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the service to look up.
|
||||
* Corresponds to the "wsdl:service" name.
|
||||
* @see javax.xml.rpc.ServiceFactory#createService(javax.xml.namespace.QName)
|
||||
* @see javax.xml.rpc.ServiceFactory#createService(java.net.URL, javax.xml.namespace.QName)
|
||||
* @see javax.xml.rpc.ServiceFactory#loadService(java.net.URL, javax.xml.namespace.QName, java.util.Properties)
|
||||
*/
|
||||
public void setServiceName(String serviceName) {
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the service.
|
||||
*/
|
||||
public String getServiceName() {
|
||||
return this.serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the JAX-RPC service interface to use for looking up the service.
|
||||
* If specified, this will override a "serviceName" setting.
|
||||
* <p>The specified interface will usually be a generated JAX-RPC service
|
||||
* interface that directly corresponds to the WSDL service declaration.
|
||||
* Note that this is not a port interface or the application-level service
|
||||
* interface to be exposed by a port proxy!
|
||||
* <p>Only supported by JAX-RPC 1.1 providers.
|
||||
* @see #setServiceName
|
||||
* @see javax.xml.rpc.ServiceFactory#loadService(Class)
|
||||
* @see javax.xml.rpc.ServiceFactory#loadService(java.net.URL, Class, java.util.Properties)
|
||||
*/
|
||||
public void setJaxRpcServiceInterface(Class jaxRpcServiceInterface) {
|
||||
this.jaxRpcServiceInterface = jaxRpcServiceInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JAX-RPC service interface to use for looking up the service.
|
||||
*/
|
||||
public Class getJaxRpcServiceInterface() {
|
||||
return this.jaxRpcServiceInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set JAX-RPC service properties to be passed to the ServiceFactory, if any.
|
||||
* <p>Only supported by JAX-RPC 1.1 providers.
|
||||
* @see javax.xml.rpc.ServiceFactory#loadService(java.net.URL, javax.xml.namespace.QName, java.util.Properties)
|
||||
* @see javax.xml.rpc.ServiceFactory#loadService(java.net.URL, Class, java.util.Properties)
|
||||
*/
|
||||
public void setJaxRpcServiceProperties(Properties jaxRpcServiceProperties) {
|
||||
this.jaxRpcServiceProperties = jaxRpcServiceProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return JAX-RPC service properties to be passed to the ServiceFactory, if any.
|
||||
*/
|
||||
public Properties getJaxRpcServiceProperties() {
|
||||
return this.jaxRpcServiceProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the JaxRpcServicePostProcessors to be applied to JAX-RPC Service
|
||||
* instances created by this factory.
|
||||
* <p>Such post-processors can, for example, register custom type mappings.
|
||||
* They are reusable across all pre-built subclasses of this factory:
|
||||
* LocalJaxRpcServiceFactoryBean, JaxRpcPortClientInterceptor,
|
||||
* JaxRpcPortProxyFactoryBean.
|
||||
* @see LocalJaxRpcServiceFactoryBean
|
||||
* @see JaxRpcPortClientInterceptor
|
||||
* @see JaxRpcPortProxyFactoryBean
|
||||
*/
|
||||
public void setServicePostProcessors(JaxRpcServicePostProcessor[] servicePostProcessors) {
|
||||
this.servicePostProcessors = servicePostProcessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JaxRpcServicePostProcessors to be applied to JAX-RPC Service
|
||||
* instances created by this factory.
|
||||
*/
|
||||
public JaxRpcServicePostProcessor[] getServicePostProcessors() {
|
||||
return this.servicePostProcessors;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a JAX-RPC Service according to the parameters of this factory.
|
||||
* @see #setServiceName
|
||||
* @see #setWsdlDocumentUrl
|
||||
* @see #postProcessJaxRpcService
|
||||
*/
|
||||
public Service createJaxRpcService() throws ServiceException {
|
||||
ServiceFactory serviceFactory = getServiceFactory();
|
||||
if (serviceFactory == null) {
|
||||
serviceFactory = createServiceFactory();
|
||||
}
|
||||
|
||||
// Create service based on this factory's settings.
|
||||
Service service = createService(serviceFactory);
|
||||
|
||||
// Allow for custom post-processing in subclasses.
|
||||
postProcessJaxRpcService(service);
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a QName for the given name, relative to the namespace URI
|
||||
* of this factory, if given.
|
||||
* @see #setNamespaceUri
|
||||
*/
|
||||
protected QName getQName(String name) {
|
||||
return (getNamespaceUri() != null ? new QName(getNamespaceUri(), name) : new QName(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JAX-RPC ServiceFactory, either of the specified class
|
||||
* or the default.
|
||||
* @throws ServiceException if thrown by JAX-RPC methods
|
||||
* @see #setServiceFactoryClass
|
||||
* @see javax.xml.rpc.ServiceFactory#newInstance()
|
||||
*/
|
||||
protected ServiceFactory createServiceFactory() throws ServiceException {
|
||||
if (getServiceFactoryClass() != null) {
|
||||
return (ServiceFactory) BeanUtils.instantiateClass(getServiceFactoryClass());
|
||||
}
|
||||
else {
|
||||
return ServiceFactory.newInstance();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually create the JAX-RPC Service instance,
|
||||
* based on this factory's settings.
|
||||
* @param serviceFactory the JAX-RPC ServiceFactory to use
|
||||
* @return the newly created JAX-RPC Service
|
||||
* @throws ServiceException if thrown by JAX-RPC methods
|
||||
* @see javax.xml.rpc.ServiceFactory#createService
|
||||
* @see javax.xml.rpc.ServiceFactory#loadService
|
||||
*/
|
||||
protected Service createService(ServiceFactory serviceFactory) throws ServiceException {
|
||||
if (getServiceName() == null && getJaxRpcServiceInterface() == null) {
|
||||
throw new IllegalArgumentException("Either 'serviceName' or 'jaxRpcServiceInterface' is required");
|
||||
}
|
||||
|
||||
if (getJaxRpcServiceInterface() != null) {
|
||||
// Create service via generated JAX-RPC service interface.
|
||||
// Only supported on JAX-RPC 1.1
|
||||
if (getWsdlDocumentUrl() != null || getJaxRpcServiceProperties() != null) {
|
||||
return serviceFactory.loadService(
|
||||
getWsdlDocumentUrl(), getJaxRpcServiceInterface(), getJaxRpcServiceProperties());
|
||||
}
|
||||
return serviceFactory.loadService(getJaxRpcServiceInterface());
|
||||
}
|
||||
|
||||
// Create service via specified JAX-RPC service name.
|
||||
QName serviceQName = getQName(getServiceName());
|
||||
if (getJaxRpcServiceProperties() != null) {
|
||||
// Only supported on JAX-RPC 1.1
|
||||
return serviceFactory.loadService(getWsdlDocumentUrl(), serviceQName, getJaxRpcServiceProperties());
|
||||
}
|
||||
if (getWsdlDocumentUrl() != null) {
|
||||
return serviceFactory.createService(getWsdlDocumentUrl(), serviceQName);
|
||||
}
|
||||
return serviceFactory.createService(serviceQName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-process the given JAX-RPC Service. Called by {@link #createJaxRpcService}.
|
||||
* Useful, for example, to register custom type mappings.
|
||||
* <p>The default implementation delegates to all registered
|
||||
* {@link JaxRpcServicePostProcessor JaxRpcServicePostProcessors}.
|
||||
* It is usually preferable to implement custom type mappings etc there rather
|
||||
* than in a subclass of this factory, to allow for reuse of the post-processors.
|
||||
* @param service the current JAX-RPC Service
|
||||
* (can be cast to an implementation-specific class if necessary)
|
||||
* @see #setServicePostProcessors
|
||||
* @see javax.xml.rpc.Service#getTypeMappingRegistry()
|
||||
*/
|
||||
protected void postProcessJaxRpcService(Service service) {
|
||||
JaxRpcServicePostProcessor[] postProcessors = getServicePostProcessors();
|
||||
if (postProcessors != null) {
|
||||
for (int i = 0; i < postProcessors.length; i++) {
|
||||
postProcessors[i].postProcessJaxRpcService(service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.jaxrpc;
|
||||
|
||||
import javax.xml.rpc.Service;
|
||||
import javax.xml.rpc.ServiceException;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.beans.factory.FactoryBean} for locally
|
||||
* defined JAX-RPC Service references.
|
||||
* Uses {@link LocalJaxRpcServiceFactory}'s facilities underneath.
|
||||
*
|
||||
* <p>Alternatively, JAX-RPC Service references can be looked up
|
||||
* in the JNDI environment of the J2EE container.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 15.12.2003
|
||||
* @see javax.xml.rpc.Service
|
||||
* @see org.springframework.jndi.JndiObjectFactoryBean
|
||||
* @see JaxRpcPortProxyFactoryBean
|
||||
* @deprecated in favor of JAX-WS support in <code>org.springframework.remoting.jaxws</code>
|
||||
*/
|
||||
@Deprecated
|
||||
public class LocalJaxRpcServiceFactoryBean extends LocalJaxRpcServiceFactory
|
||||
implements FactoryBean<Service>, InitializingBean {
|
||||
|
||||
private Service service;
|
||||
|
||||
|
||||
public void afterPropertiesSet() throws ServiceException {
|
||||
this.service = createJaxRpcService();
|
||||
}
|
||||
|
||||
|
||||
public Service getObject() throws Exception {
|
||||
return this.service;
|
||||
}
|
||||
|
||||
public Class<? extends Service> getObjectType() {
|
||||
return (this.service != null ? this.service.getClass() : Service.class);
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.remoting.jaxrpc;
|
||||
|
||||
import java.io.File;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.xml.rpc.ServiceException;
|
||||
import javax.xml.rpc.server.ServiceLifecycle;
|
||||
import javax.xml.rpc.server.ServletEndpointContext;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
* Convenience base class for JAX-RPC servlet endpoint implementations.
|
||||
* Provides a reference to the current Spring application context,
|
||||
* e.g. for bean lookup or resource loading.
|
||||
*
|
||||
* <p>The Web Service servlet needs to run in the same web application
|
||||
* as the Spring context to allow for access to Spring's facilities.
|
||||
* In case of Axis, copy the AxisServlet definition into your web.xml,
|
||||
* and set up the endpoint in "server-config.wsdd" (or use the deploy tool).
|
||||
*
|
||||
* <p>This class does not extend
|
||||
* {@link org.springframework.web.context.support.WebApplicationObjectSupport}
|
||||
* to not expose any public setters. For some reason, Axis tries to
|
||||
* resolve public setters in a special way...
|
||||
*
|
||||
* <p>JAX-RPC service endpoints are usually required to implement an
|
||||
* RMI port interface. However, many JAX-RPC implementations accept plain
|
||||
* service endpoint classes too, avoiding the need to maintain an RMI port
|
||||
* interface in addition to an existing non-RMI business interface.
|
||||
* Therefore, implementing the business interface will usually be sufficient.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 16.12.2003
|
||||
* @see #init
|
||||
* @see #getWebApplicationContext
|
||||
* @deprecated in favor of JAX-WS support in <code>org.springframework.remoting.jaxws</code>
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class ServletEndpointSupport implements ServiceLifecycle {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private ServletEndpointContext servletEndpointContext;
|
||||
|
||||
private WebApplicationContext webApplicationContext;
|
||||
|
||||
private MessageSourceAccessor messageSourceAccessor;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize this JAX-RPC servlet endpoint.
|
||||
* Calls onInit after successful context initialization.
|
||||
* @param context ServletEndpointContext
|
||||
* @throws ServiceException if the context is not a ServletEndpointContext
|
||||
* @see #onInit
|
||||
*/
|
||||
public final void init(Object context) throws ServiceException {
|
||||
if (!(context instanceof ServletEndpointContext)) {
|
||||
throw new ServiceException("ServletEndpointSupport needs ServletEndpointContext, not [" + context + "]");
|
||||
}
|
||||
this.servletEndpointContext = (ServletEndpointContext) context;
|
||||
ServletContext servletContext = this.servletEndpointContext.getServletContext();
|
||||
this.webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
|
||||
this.messageSourceAccessor = new MessageSourceAccessor(this.webApplicationContext);
|
||||
onInit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current JAX-RPC ServletEndpointContext.
|
||||
*/
|
||||
protected final ServletEndpointContext getServletEndpointContext() {
|
||||
return this.servletEndpointContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current Spring ApplicationContext.
|
||||
*/
|
||||
protected final ApplicationContext getApplicationContext() {
|
||||
return this.webApplicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current Spring WebApplicationContext.
|
||||
*/
|
||||
protected final WebApplicationContext getWebApplicationContext() {
|
||||
return this.webApplicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a MessageSourceAccessor for the application context
|
||||
* used by this object, for easy message access.
|
||||
*/
|
||||
protected final MessageSourceAccessor getMessageSourceAccessor() {
|
||||
return this.messageSourceAccessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current ServletContext.
|
||||
*/
|
||||
protected final ServletContext getServletContext() {
|
||||
return this.webApplicationContext.getServletContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the temporary directory for the current web application,
|
||||
* as provided by the servlet container.
|
||||
* @return the File representing the temporary directory
|
||||
*/
|
||||
protected final File getTempDir() {
|
||||
return WebUtils.getTempDir(getServletContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for custom initialization after the context has been set up.
|
||||
* @throws ServiceException if initialization failed
|
||||
*/
|
||||
protected void onInit() throws ServiceException {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This implementation of destroy is empty.
|
||||
* Can be overridden in subclasses.
|
||||
*/
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user