/*
 * Copyright 2002-2018 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.lang.Nullable;
import org.springframework.remoting.rmi.CodebaseAwareObjectInputStream;
import org.springframework.remoting.support.RemoteInvocation;
import org.springframework.remoting.support.RemoteInvocationResult;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

Abstract base implementation of the HttpInvokerRequestExecutor interface.

Pre-implements serialization of RemoteInvocation objects and deserialization of RemoteInvocationResults objects.

Author:Juergen Hoeller
See Also:
Since:1.1
/** * 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".
/** * Default content type: "application/x-java-serialized-object". */
public static final String CONTENT_TYPE_SERIALIZED_OBJECT = "application/x-java-serialized-object"; private static final int SERIALIZED_INVOCATION_BYTE_ARRAY_INITIAL_SIZE = 1024; 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"; protected final Log logger = LogFactory.getLog(getClass()); private String contentType = CONTENT_TYPE_SERIALIZED_OBJECT; private boolean acceptGzipEncoding = true; @Nullable private ClassLoader beanClassLoader;
Specify the content type to use for sending HTTP invoker requests.

Default is "application/x-java-serialized-object".

/** * 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.
/** * 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.

Default is "true". Turn this flag off if you do not want GZIP response compression even if enabled on the HTTP server.

/** * 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.
/** * 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; } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; }
Return the bean ClassLoader that this executor is supposed to use.
/** * Return the bean ClassLoader that this executor is supposed to use. */
@Nullable protected ClassLoader getBeanClassLoader() { return this.beanClassLoader; } @Override 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.
Params:
  • invocation – the RemoteInvocation object
Throws:
Returns:a ByteArrayOutputStream with the serialized RemoteInvocation
/** * 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.

The default implementation gives decorateOutputStream a chance to decorate the stream first (for example, for custom encryption or compression). Creates an ObjectOutputStream for the final stream and calls doWriteRemoteInvocation to actually write the object.

Can be overridden for custom serialization of the invocation.

Params:
  • invocation – the RemoteInvocation object
  • os – the OutputStream to write to
Throws:
See Also:
/** * Serialize the given RemoteInvocation to the given OutputStream. * <p>The default implementation gives {@code decorateOutputStream} a chance * to decorate the stream first (for example, for custom encryption or compression). * Creates an {@code ObjectOutputStream} for the final stream and calls * {@code doWriteRemoteInvocation} 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.

The default implementation returns the given stream as-is. Can be overridden, for example, for custom encryption or compression.

Params:
  • os – the original OutputStream
Returns:the potentially decorated OutputStream
/** * 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.

The default implementation simply calls writeObject. Can be overridden for serialization of a custom wrapper object rather than the plain invocation, for example an encryption-aware holder.

Params:
  • invocation – the RemoteInvocation object
  • oos – the ObjectOutputStream to write to
Throws:
See Also:
/** * Perform the actual writing of the given invocation object to the * given ObjectOutputStream. * <p>The default implementation simply calls {@code writeObject}. * 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.

Implementations will usually call readRemoteInvocationResult to deserialize a returned RemoteInvocationResult object.

Params:
  • config – the HTTP invoker configuration that specifies the target service
  • baos – the ByteArrayOutputStream that contains the serialized RemoteInvocation object
Throws:
See Also:
Returns:the RemoteInvocationResult object
/** * Execute a request to send the given serialized remote invocation. * <p>Implementations will usually call {@code readRemoteInvocationResult} * 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.

Gives decorateInputStream a chance to decorate the stream first (for example, for custom encryption or compression). Creates an ObjectInputStream via createObjectInputStream and calls doReadRemoteInvocationResult to actually read the object.

Can be overridden for custom serialization of the invocation.

Params:
  • is – the InputStream to read from
  • codebaseUrl – the codebase URL to load classes from if not found locally
Throws:
See Also:
Returns:the RemoteInvocationResult object
/** * Deserialize a RemoteInvocationResult object from the given InputStream. * <p>Gives {@code decorateInputStream} a chance to decorate the stream * first (for example, for custom encryption or compression). Creates an * {@code ObjectInputStream} via {@code createObjectInputStream} and * calls {@code doReadRemoteInvocationResult} 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, @Nullable 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.

The default implementation returns the given stream as-is. Can be overridden, for example, for custom encryption or compression.

Params:
  • is – the original InputStream
Returns:the potentially decorated InputStream
/** * 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.
Params:
  • is – the InputStream to read from
  • codebaseUrl – the codebase URL to load classes from if not found locally (can be null)
Throws:
  • IOException – if creation of the ObjectInputStream failed
See Also:
Returns:the new ObjectInputStream instance to use
/** * 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}) * @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, @Nullable String codebaseUrl) throws IOException { return new CodebaseAwareObjectInputStream(is, getBeanClassLoader(), codebaseUrl); }
Perform the actual reading of an invocation object from the given ObjectInputStream.

The default implementation simply calls readObject. Can be overridden for deserialization of a custom wrapper object rather than the plain invocation, for example an encryption-aware holder.

Params:
  • ois – the ObjectInputStream to read from
Throws:
See Also:
Returns:the RemoteInvocationResult object
/** * Perform the actual reading of an invocation object from the * given ObjectInputStream. * <p>The default implementation simply calls {@code readObject}. * 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() + "]: " + ClassUtils.getDescriptiveType(obj)); } return (RemoteInvocationResult) obj; } }