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.IOException;
020: import java.io.InputStream;
021: import java.io.ObjectInputStream;
022: import java.io.ObjectOutputStream;
023: import java.io.OutputStream;
024: import java.rmi.RemoteException;
025:
026: import javax.servlet.ServletException;
027: import javax.servlet.http.HttpServletRequest;
028: import javax.servlet.http.HttpServletResponse;
029:
030: import org.springframework.beans.factory.InitializingBean;
031: import org.springframework.remoting.rmi.CodebaseAwareObjectInputStream;
032: import org.springframework.remoting.support.RemoteInvocation;
033: import org.springframework.remoting.support.RemoteInvocationBasedExporter;
034: import org.springframework.remoting.support.RemoteInvocationResult;
035: import org.springframework.util.Assert;
036: import org.springframework.web.HttpRequestHandler;
037: import org.springframework.web.util.NestedServletException;
038:
039: /**
040: * HTTP request handler that exports the specified service bean as HTTP invoker
041: * service endpoint, accessible via an HTTP invoker proxy.
042: *
043: * <p>Deserializes remote invocation objects and serializes remote invocation
044: * result objects. Uses Java serialization just like RMI, but provides the
045: * same ease of setup as Caucho's HTTP-based Hessian and Burlap protocols.
046: *
047: * <p><b>HTTP invoker is the recommended protocol for Java-to-Java remoting.</b>
048: * It is more powerful and more extensible than Hessian and Burlap, at the
049: * expense of being tied to Java. Nevertheless, it is as easy to set up as
050: * Hessian and Burlap, which is its main advantage compared to RMI.
051: *
052: * @author Juergen Hoeller
053: * @since 1.1
054: * @see HttpInvokerClientInterceptor
055: * @see HttpInvokerProxyFactoryBean
056: * @see org.springframework.remoting.rmi.RmiServiceExporter
057: * @see org.springframework.remoting.caucho.HessianServiceExporter
058: * @see org.springframework.remoting.caucho.BurlapServiceExporter
059: */
060: public class HttpInvokerServiceExporter extends
061: RemoteInvocationBasedExporter implements HttpRequestHandler,
062: InitializingBean {
063:
064: /**
065: * Default content type: "application/x-java-serialized-object"
066: */
067: public static final String CONTENT_TYPE_SERIALIZED_OBJECT = "application/x-java-serialized-object";
068:
069: private String contentType = CONTENT_TYPE_SERIALIZED_OBJECT;
070:
071: private Object proxy;
072:
073: /**
074: * Specify the content type to use for sending HTTP invoker responses.
075: * <p>Default is "application/x-java-serialized-object".
076: */
077: public void setContentType(String contentType) {
078: Assert.notNull(contentType, "'contentType' must not be null");
079: this .contentType = contentType;
080: }
081:
082: /**
083: * Return the content type to use for sending HTTP invoker responses.
084: */
085: public String getContentType() {
086: return this .contentType;
087: }
088:
089: public void afterPropertiesSet() {
090: prepare();
091: }
092:
093: /**
094: * Initialize this service exporter.
095: */
096: public void prepare() {
097: this .proxy = getProxyForService();
098: }
099:
100: /**
101: * Reads a remote invocation from the request, executes it,
102: * and writes the remote invocation result to the response.
103: * @see #readRemoteInvocation(HttpServletRequest)
104: * @see #invokeAndCreateResult(org.springframework.remoting.support.RemoteInvocation, Object)
105: * @see #writeRemoteInvocationResult(HttpServletRequest, HttpServletResponse, RemoteInvocationResult)
106: */
107: public void handleRequest(HttpServletRequest request,
108: HttpServletResponse response) throws ServletException,
109: IOException {
110:
111: Assert.notNull(this .proxy,
112: "HttpInvokerServiceExporter has not been initialized");
113:
114: try {
115: RemoteInvocation invocation = readRemoteInvocation(request);
116: RemoteInvocationResult result = invokeAndCreateResult(
117: invocation, this .proxy);
118: writeRemoteInvocationResult(request, response, result);
119: } catch (ClassNotFoundException ex) {
120: throw new NestedServletException(
121: "Class not found during deserialization", ex);
122: }
123: }
124:
125: /**
126: * Read a RemoteInvocation from the given HTTP request.
127: * <p>Delegates to
128: * {@link #readRemoteInvocation(javax.servlet.http.HttpServletRequest, java.io.InputStream)}
129: * with the
130: * {@link javax.servlet.ServletRequest#getInputStream() servlet request's input stream}.
131: * @param request current HTTP request
132: * @return the RemoteInvocation object
133: * @throws IOException in case of I/O failure
134: * @throws ClassNotFoundException if thrown by deserialization
135: */
136: protected RemoteInvocation readRemoteInvocation(
137: HttpServletRequest request) throws IOException,
138: ClassNotFoundException {
139:
140: return readRemoteInvocation(request, request.getInputStream());
141: }
142:
143: /**
144: * Deserialize a RemoteInvocation object from the given InputStream.
145: * <p>Gives {@link #decorateInputStream} a chance to decorate the stream
146: * first (for example, for custom encryption or compression). Creates a
147: * {@link org.springframework.remoting.rmi.CodebaseAwareObjectInputStream}
148: * and calls {@link #doReadRemoteInvocation} to actually read the object.
149: * <p>Can be overridden for custom serialization of the invocation.
150: * @param request current HTTP request
151: * @param is the InputStream to read from
152: * @return the RemoteInvocation object
153: * @throws IOException in case of I/O failure
154: * @throws ClassNotFoundException if thrown during deserialization
155: */
156: protected RemoteInvocation readRemoteInvocation(
157: HttpServletRequest request, InputStream is)
158: throws IOException, ClassNotFoundException {
159:
160: ObjectInputStream ois = createObjectInputStream(decorateInputStream(
161: request, is));
162: try {
163: return doReadRemoteInvocation(ois);
164: } finally {
165: ois.close();
166: }
167: }
168:
169: /**
170: * Return the InputStream to use for reading remote invocations,
171: * potentially decorating the given original InputStream.
172: * <p>The default implementation returns the given stream as-is.
173: * Can be overridden, for example, for custom encryption or compression.
174: * @param request current HTTP request
175: * @param is the original InputStream
176: * @return the potentially decorated InputStream
177: * @throws IOException in case of I/O failure
178: */
179: protected InputStream decorateInputStream(
180: HttpServletRequest request, InputStream is)
181: throws IOException {
182: return is;
183: }
184:
185: /**
186: * Create an ObjectInputStream for the given InputStream.
187: * <p>The default implementation creates a Spring
188: * {@link org.springframework.remoting.rmi.CodebaseAwareObjectInputStream}.
189: * <p>Spring's CodebaseAwareObjectInputStream is used to explicitly resolve
190: * primitive class names. This is done by the standard ObjectInputStream
191: * on JDK 1.4+, but needs to be done explicitly on JDK 1.3.
192: * @param is the InputStream to read from
193: * @return the new ObjectInputStream instance to use
194: * @throws IOException if creation of the ObjectInputStream failed
195: */
196: protected ObjectInputStream createObjectInputStream(InputStream is)
197: throws IOException {
198: return new CodebaseAwareObjectInputStream(is, null);
199: }
200:
201: /**
202: * Perform the actual reading of an invocation result object from the
203: * given ObjectInputStream.
204: * <p>The default implementation simply calls
205: * {@link java.io.ObjectInputStream#readObject()}.
206: * Can be overridden for deserialization of a custom wrapper object rather
207: * than the plain invocation, for example an encryption-aware holder.
208: * @param ois the ObjectInputStream to read from
209: * @return the RemoteInvocationResult object
210: * @throws IOException in case of I/O failure
211: * @throws ClassNotFoundException if case of a transferred class not
212: * being found in the local ClassLoader
213: */
214: protected RemoteInvocation doReadRemoteInvocation(
215: ObjectInputStream ois) throws IOException,
216: ClassNotFoundException {
217:
218: Object obj = ois.readObject();
219: if (!(obj instanceof RemoteInvocation)) {
220: throw new RemoteException(
221: "Deserialized object needs to be assignable to type ["
222: + RemoteInvocation.class.getName() + "]: "
223: + obj);
224: }
225: return (RemoteInvocation) obj;
226: }
227:
228: /**
229: * Write the given RemoteInvocationResult to the given HTTP response.
230: * @param request current HTTP request
231: * @param response current HTTP response
232: * @param result the RemoteInvocationResult object
233: * @throws IOException in case of I/O failure
234: */
235: protected void writeRemoteInvocationResult(
236: HttpServletRequest request, HttpServletResponse response,
237: RemoteInvocationResult result) throws IOException {
238:
239: response.setContentType(getContentType());
240: writeRemoteInvocationResult(request, response, result, response
241: .getOutputStream());
242: }
243:
244: /**
245: * Serialize the given RemoteInvocation to the given OutputStream.
246: * <p>The default implementation gives {@link #decorateOutputStream} a chance
247: * to decorate the stream first (for example, for custom encryption or compression).
248: * Creates an {@link java.io.ObjectOutputStream} for the final stream and calls
249: * {@link #doWriteRemoteInvocationResult} to actually write the object.
250: * <p>Can be overridden for custom serialization of the invocation.
251: * @param request current HTTP request
252: * @param response current HTTP response
253: * @param result the RemoteInvocationResult object
254: * @param os the OutputStream to write to
255: * @throws IOException in case of I/O failure
256: * @see #decorateOutputStream
257: * @see #doWriteRemoteInvocationResult
258: */
259: protected void writeRemoteInvocationResult(
260: HttpServletRequest request, HttpServletResponse response,
261: RemoteInvocationResult result, OutputStream os)
262: throws IOException {
263:
264: ObjectOutputStream oos = createObjectOutputStream(decorateOutputStream(
265: request, response, os));
266: try {
267: doWriteRemoteInvocationResult(result, oos);
268: oos.flush();
269: } finally {
270: oos.close();
271: }
272: }
273:
274: /**
275: * Return the OutputStream to use for writing remote invocation results,
276: * potentially decorating the given original OutputStream.
277: * <p>The default implementation returns the given stream as-is.
278: * Can be overridden, for example, for custom encryption or compression.
279: * @param request current HTTP request
280: * @param response current HTTP response
281: * @param os the original OutputStream
282: * @return the potentially decorated OutputStream
283: * @throws IOException in case of I/O failure
284: */
285: protected OutputStream decorateOutputStream(
286: HttpServletRequest request, HttpServletResponse response,
287: OutputStream os) throws IOException {
288:
289: return os;
290: }
291:
292: /**
293: * Create an ObjectOutputStream for the given OutputStream.
294: * <p>The default implementation creates a plain
295: * {@link java.io.ObjectOutputStream}.
296: * @param os the OutputStream to write to
297: * @return the new ObjectOutputStream instance to use
298: * @throws IOException if creation of the ObjectOutputStream failed
299: */
300: protected ObjectOutputStream createObjectOutputStream(
301: OutputStream os) throws IOException {
302: return new ObjectOutputStream(os);
303: }
304:
305: /**
306: * Perform the actual writing of the given invocation result object
307: * to the given ObjectOutputStream.
308: * <p>The default implementation simply calls
309: * {@link java.io.ObjectOutputStream#writeObject}.
310: * Can be overridden for serialization of a custom wrapper object rather
311: * than the plain invocation, for example an encryption-aware holder.
312: * @param result the RemoteInvocationResult object
313: * @param oos the ObjectOutputStream to write to
314: * @throws IOException if thrown by I/O methods
315: */
316: protected void doWriteRemoteInvocationResult(
317: RemoteInvocationResult result, ObjectOutputStream oos)
318: throws IOException {
319:
320: oos.writeObject(result);
321: }
322:
323: }
|