001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.remoting.httpinvoker;
018:
019: import java.io.ByteArrayOutputStream;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.io.ObjectInputStream;
023: import java.io.ObjectOutputStream;
024: import java.io.OutputStream;
025: import java.rmi.RemoteException;
026:
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029:
030: import org.springframework.beans.factory.BeanClassLoaderAware;
031: import org.springframework.remoting.rmi.CodebaseAwareObjectInputStream;
032: import org.springframework.remoting.support.RemoteInvocation;
033: import org.springframework.remoting.support.RemoteInvocationResult;
034: import org.springframework.util.Assert;
035:
036: /**
037: * Abstract base implementation of the HttpInvokerRequestExecutor interface.
038: *
039: * <p>Pre-implements serialization of RemoteInvocation objects and
040: * deserialization of RemoteInvocationResults objects.
041: *
042: * @author Juergen Hoeller
043: * @since 1.1
044: * @see #doExecuteRequest
045: */
046: public abstract class AbstractHttpInvokerRequestExecutor implements
047: HttpInvokerRequestExecutor, BeanClassLoaderAware {
048:
049: /**
050: * Default content type: "application/x-java-serialized-object"
051: */
052: public static final String CONTENT_TYPE_SERIALIZED_OBJECT = "application/x-java-serialized-object";
053:
054: protected static final String HTTP_METHOD_POST = "POST";
055:
056: protected static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type";
057:
058: protected static final String HTTP_HEADER_CONTENT_LENGTH = "Content-Length";
059:
060: protected static final String HTTP_HEADER_ACCEPT_ENCODING = "Accept-Encoding";
061:
062: protected static final String HTTP_HEADER_CONTENT_ENCODING = "Content-Encoding";
063:
064: protected static final String ENCODING_GZIP = "gzip";
065:
066: private static final int SERIALIZED_INVOCATION_BYTE_ARRAY_INITIAL_SIZE = 1024;
067:
068: protected final Log logger = LogFactory.getLog(getClass());
069:
070: private String contentType = CONTENT_TYPE_SERIALIZED_OBJECT;
071:
072: private boolean acceptGzipEncoding = true;
073:
074: private ClassLoader beanClassLoader;
075:
076: /**
077: * Specify the content type to use for sending HTTP invoker requests.
078: * <p>Default is "application/x-java-serialized-object".
079: */
080: public void setContentType(String contentType) {
081: Assert.notNull(contentType, "'contentType' must not be null");
082: this .contentType = contentType;
083: }
084:
085: /**
086: * Return the content type to use for sending HTTP invoker requests.
087: */
088: public String getContentType() {
089: return this .contentType;
090: }
091:
092: /**
093: * Set whether to accept GZIP encoding, that is, whether to
094: * send the HTTP "Accept-Encoding" header with "gzip" as value.
095: * <p>Default is "true". Turn this flag off if you do not want
096: * GZIP response compression even if enabled on the HTTP server.
097: */
098: public void setAcceptGzipEncoding(boolean acceptGzipEncoding) {
099: this .acceptGzipEncoding = acceptGzipEncoding;
100: }
101:
102: /**
103: * Return whether to accept GZIP encoding, that is, whether to
104: * send the HTTP "Accept-Encoding" header with "gzip" as value.
105: */
106: public boolean isAcceptGzipEncoding() {
107: return this .acceptGzipEncoding;
108: }
109:
110: public void setBeanClassLoader(ClassLoader classLoader) {
111: this .beanClassLoader = classLoader;
112: }
113:
114: /**
115: * Return the bean ClassLoader that this executor is supposed to use.
116: */
117: protected ClassLoader getBeanClassLoader() {
118: return this .beanClassLoader;
119: }
120:
121: public final RemoteInvocationResult executeRequest(
122: HttpInvokerClientConfiguration config,
123: RemoteInvocation invocation) throws Exception {
124:
125: ByteArrayOutputStream baos = getByteArrayOutputStream(invocation);
126: if (logger.isDebugEnabled()) {
127: logger
128: .debug("Sending HTTP invoker request for service at ["
129: + config.getServiceUrl()
130: + "], with size "
131: + baos.size());
132: }
133: return doExecuteRequest(config, baos);
134: }
135:
136: /**
137: * Serialize the given RemoteInvocation into a ByteArrayOutputStream.
138: * @param invocation the RemoteInvocation object
139: * @return a ByteArrayOutputStream with the serialized RemoteInvocation
140: * @throws IOException if thrown by I/O methods
141: */
142: protected ByteArrayOutputStream getByteArrayOutputStream(
143: RemoteInvocation invocation) throws IOException {
144: ByteArrayOutputStream baos = new ByteArrayOutputStream(
145: SERIALIZED_INVOCATION_BYTE_ARRAY_INITIAL_SIZE);
146: writeRemoteInvocation(invocation, baos);
147: return baos;
148: }
149:
150: /**
151: * Serialize the given RemoteInvocation to the given OutputStream.
152: * <p>The default implementation gives <code>decorateOutputStream</code> a chance
153: * to decorate the stream first (for example, for custom encryption or compression).
154: * Creates an <code>ObjectOutputStream</code> for the final stream and calls
155: * <code>doWriteRemoteInvocation</code> to actually write the object.
156: * <p>Can be overridden for custom serialization of the invocation.
157: * @param invocation the RemoteInvocation object
158: * @param os the OutputStream to write to
159: * @throws IOException if thrown by I/O methods
160: * @see #decorateOutputStream
161: * @see #doWriteRemoteInvocation
162: */
163: protected void writeRemoteInvocation(RemoteInvocation invocation,
164: OutputStream os) throws IOException {
165: ObjectOutputStream oos = new ObjectOutputStream(
166: decorateOutputStream(os));
167: try {
168: doWriteRemoteInvocation(invocation, oos);
169: oos.flush();
170: } finally {
171: oos.close();
172: }
173: }
174:
175: /**
176: * Return the OutputStream to use for writing remote invocations,
177: * potentially decorating the given original OutputStream.
178: * <p>The default implementation returns the given stream as-is.
179: * Can be overridden, for example, for custom encryption or compression.
180: * @param os the original OutputStream
181: * @return the potentially decorated OutputStream
182: */
183: protected OutputStream decorateOutputStream(OutputStream os)
184: throws IOException {
185: return os;
186: }
187:
188: /**
189: * Perform the actual writing of the given invocation object to the
190: * given ObjectOutputStream.
191: * <p>The default implementation simply calls <code>writeObject</code>.
192: * Can be overridden for serialization of a custom wrapper object rather
193: * than the plain invocation, for example an encryption-aware holder.
194: * @param invocation the RemoteInvocation object
195: * @param oos the ObjectOutputStream to write to
196: * @throws IOException if thrown by I/O methods
197: * @see java.io.ObjectOutputStream#writeObject
198: */
199: protected void doWriteRemoteInvocation(RemoteInvocation invocation,
200: ObjectOutputStream oos) throws IOException {
201: oos.writeObject(invocation);
202: }
203:
204: /**
205: * Execute a request to send the given serialized remote invocation.
206: * <p>Implementations will usually call <code>readRemoteInvocationResult</code>
207: * to deserialize a returned RemoteInvocationResult object.
208: * @param config the HTTP invoker configuration that specifies the
209: * target service
210: * @param baos the ByteArrayOutputStream that contains the serialized
211: * RemoteInvocation object
212: * @return the RemoteInvocationResult object
213: * @throws IOException if thrown by I/O operations
214: * @throws ClassNotFoundException if thrown during deserialization
215: * @throws Exception in case of general errors
216: * @see #readRemoteInvocationResult(java.io.InputStream, String)
217: */
218: protected abstract RemoteInvocationResult doExecuteRequest(
219: HttpInvokerClientConfiguration config,
220: ByteArrayOutputStream baos) throws Exception;
221:
222: /**
223: * Deserialize a RemoteInvocationResult object from the given InputStream.
224: * <p>Gives <code>decorateInputStream</code> a chance to decorate the stream
225: * first (for example, for custom encryption or compression). Creates an
226: * <code>ObjectInputStream</code> via <code>createObjectInputStream</code> and
227: * calls <code>doReadRemoteInvocationResult</code> to actually read the object.
228: * <p>Can be overridden for custom serialization of the invocation.
229: * @param is the InputStream to read from
230: * @param codebaseUrl the codebase URL to load classes from if not found locally
231: * @return the RemoteInvocationResult object
232: * @throws IOException if thrown by I/O methods
233: * @throws ClassNotFoundException if thrown during deserialization
234: * @see #decorateInputStream
235: * @see #createObjectInputStream
236: * @see #doReadRemoteInvocationResult
237: */
238: protected RemoteInvocationResult readRemoteInvocationResult(
239: InputStream is, String codebaseUrl) throws IOException,
240: ClassNotFoundException {
241:
242: ObjectInputStream ois = createObjectInputStream(
243: decorateInputStream(is), codebaseUrl);
244: try {
245: return doReadRemoteInvocationResult(ois);
246: } finally {
247: ois.close();
248: }
249: }
250:
251: /**
252: * Return the InputStream to use for reading remote invocation results,
253: * potentially decorating the given original InputStream.
254: * <p>The default implementation returns the given stream as-is.
255: * Can be overridden, for example, for custom encryption or compression.
256: * @param is the original InputStream
257: * @return the potentially decorated InputStream
258: */
259: protected InputStream decorateInputStream(InputStream is)
260: throws IOException {
261: return is;
262: }
263:
264: /**
265: * Create an ObjectInputStream for the given InputStream and codebase.
266: * The default implementation creates a CodebaseAwareObjectInputStream.
267: * @param is the InputStream to read from
268: * @param codebaseUrl the codebase URL to load classes from if not found locally
269: * (can be <code>null</code>)
270: * @return the new ObjectInputStream instance to use
271: * @throws IOException if creation of the ObjectInputStream failed
272: * @see org.springframework.remoting.rmi.CodebaseAwareObjectInputStream
273: */
274: protected ObjectInputStream createObjectInputStream(InputStream is,
275: String codebaseUrl) throws IOException {
276: return new CodebaseAwareObjectInputStream(is,
277: getBeanClassLoader(), codebaseUrl);
278: }
279:
280: /**
281: * Perform the actual reading of an invocation object from the
282: * given ObjectInputStream.
283: * <p>The default implementation simply calls <code>readObject</code>.
284: * Can be overridden for deserialization of a custom wrapper object rather
285: * than the plain invocation, for example an encryption-aware holder.
286: * @param ois the ObjectInputStream to read from
287: * @return the RemoteInvocationResult object
288: * @throws IOException if thrown by I/O methods
289: * @see java.io.ObjectOutputStream#writeObject
290: */
291: protected RemoteInvocationResult doReadRemoteInvocationResult(
292: ObjectInputStream ois) throws IOException,
293: ClassNotFoundException {
294:
295: Object obj = ois.readObject();
296: if (!(obj instanceof RemoteInvocationResult)) {
297: throw new RemoteException(
298: "Deserialized object needs to be assignable to type ["
299: + RemoteInvocationResult.class.getName()
300: + "]: " + obj);
301: }
302: return (RemoteInvocationResult) obj;
303: }
304:
305: }
|