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:
Chris Beams
2012-01-20 22:51:02 +01:00
parent b6cb514d38
commit 02a4473c62
5671 changed files with 20 additions and 32 deletions

View File

@@ -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&lt;String&gt; entity = new HttpEntity&lt;String&gt;(helloWorld, headers);
* URI location = template.postForLocation("http://example.com", entity);
* </pre>
* or
* <pre class="code">
* HttpEntity&lt;String&gt; 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">
* &#64;RequestMapping("/handle")
* public HttpEntity&lt;String&gt; handle() {
* HttpHeaders responseHeaders = new HttpHeaders();
* responseHeaders.set("MyResponseHeader", "MyValue");
* return new HttpEntity&lt;String&gt;("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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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 + "]");
}
}
}

View 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>&#42;/&#42;</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>&#42;</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>&#42;</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>&#42;</code> or not.
* @return whether the subtype is <code>&#42;</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>&#42;</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 &lt; audio/* &lt; *&#047;*</blockquote>
* <blockquote>audio/* &lt; audio/*;q=0.7; audio/*;q=0.3</blockquote>
* <blockquote>audio/basic;level=1 &lt; 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
}
}
}
};
}

View File

@@ -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() : "");
}
}

View File

@@ -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&lt;String&gt; 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">
* &#64;RequestMapping("/handle")
* public ResponseEntity&lt;String&gt; handle() {
* HttpHeaders responseHeaders = new HttpHeaders();
* responseHeaders.set("MyResponseHeader", "MyValue");
* return new ResponseEntity&lt;String&gt;("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();
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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 {
}
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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) {
}
}

View File

@@ -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>&#42;&#47;&#42;</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());
}
}

View File

@@ -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&lt;String, String&gt;}), and it can write multipart form (as {@link MultiValueMap
* MultiValueMap&lt;String, Object&gt;}. 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&lt;String, String&gt; form =
* new LinkedMultiValueMap&lt;String, String&gt;(); 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&lt;String, Object&gt; parts = new
* LinkedMultiValueMap&lt;String, Object&gt;(); 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);
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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>&#42;&#47;&#42;</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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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&lt;?&gt; 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;
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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());
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 {
}

View File

@@ -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();
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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"));
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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