001/*
002 * Copyright 2002-2017 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 *      https://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
017package org.springframework.remoting.rmi;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.io.ObjectInputStream;
022import java.io.ObjectOutputStream;
023import java.io.OutputStream;
024import java.rmi.RemoteException;
025
026import org.springframework.beans.factory.InitializingBean;
027import org.springframework.remoting.support.RemoteInvocation;
028import org.springframework.remoting.support.RemoteInvocationBasedExporter;
029import org.springframework.remoting.support.RemoteInvocationResult;
030import org.springframework.util.Assert;
031import org.springframework.util.ClassUtils;
032
033/**
034 * Abstract base class for remote service exporters that explicitly deserialize
035 * {@link org.springframework.remoting.support.RemoteInvocation} objects and serialize
036 * {@link org.springframework.remoting.support.RemoteInvocationResult} objects,
037 * for example Spring's HTTP invoker.
038 *
039 * <p>Provides template methods for {@code ObjectInputStream} and
040 * {@code ObjectOutputStream} handling.
041 *
042 * @author Juergen Hoeller
043 * @since 2.5.1
044 * @see java.io.ObjectInputStream
045 * @see java.io.ObjectOutputStream
046 * @see #doReadRemoteInvocation
047 * @see #doWriteRemoteInvocationResult
048 */
049public abstract class RemoteInvocationSerializingExporter extends RemoteInvocationBasedExporter
050                implements InitializingBean {
051
052        /**
053         * Default content type: "application/x-java-serialized-object"
054         */
055        public static final String CONTENT_TYPE_SERIALIZED_OBJECT = "application/x-java-serialized-object";
056
057
058        private String contentType = CONTENT_TYPE_SERIALIZED_OBJECT;
059
060        private boolean acceptProxyClasses = true;
061
062        private Object proxy;
063
064
065        /**
066         * Specify the content type to use for sending remote invocation responses.
067         * <p>Default is "application/x-java-serialized-object".
068         */
069        public void setContentType(String contentType) {
070                Assert.notNull(contentType, "'contentType' must not be null");
071                this.contentType = contentType;
072        }
073
074        /**
075         * Return the content type to use for sending remote invocation responses.
076         */
077        public String getContentType() {
078                return this.contentType;
079        }
080
081        /**
082         * Set whether to accept deserialization of proxy classes.
083         * <p>Default is "true". May be deactivated as a security measure.
084         */
085        public void setAcceptProxyClasses(boolean acceptProxyClasses) {
086                this.acceptProxyClasses = acceptProxyClasses;
087        }
088
089        /**
090         * Return whether to accept deserialization of proxy classes.
091         */
092        public boolean isAcceptProxyClasses() {
093                return this.acceptProxyClasses;
094        }
095
096
097        @Override
098        public void afterPropertiesSet() {
099                prepare();
100        }
101
102        /**
103         * Initialize this service exporter.
104         */
105        public void prepare() {
106                this.proxy = getProxyForService();
107        }
108
109        protected final Object getProxy() {
110                if (this.proxy == null) {
111                        throw new IllegalStateException(ClassUtils.getShortName(getClass()) + " has not been initialized");
112                }
113                return this.proxy;
114        }
115
116
117        /**
118         * Create an ObjectInputStream for the given InputStream.
119         * <p>The default implementation creates a Spring {@link CodebaseAwareObjectInputStream}.
120         * @param is the InputStream to read from
121         * @return the new ObjectInputStream instance to use
122         * @throws java.io.IOException if creation of the ObjectInputStream failed
123         */
124        protected ObjectInputStream createObjectInputStream(InputStream is) throws IOException {
125                return new CodebaseAwareObjectInputStream(is, getBeanClassLoader(), isAcceptProxyClasses());
126        }
127
128        /**
129         * Perform the actual reading of an invocation result object from the
130         * given ObjectInputStream.
131         * <p>The default implementation simply calls
132         * {@link java.io.ObjectInputStream#readObject()}.
133         * Can be overridden for deserialization of a custom wrapper object rather
134         * than the plain invocation, for example an encryption-aware holder.
135         * @param ois the ObjectInputStream to read from
136         * @return the RemoteInvocationResult object
137         * @throws java.io.IOException in case of I/O failure
138         * @throws ClassNotFoundException if case of a transferred class not
139         * being found in the local ClassLoader
140         */
141        protected RemoteInvocation doReadRemoteInvocation(ObjectInputStream ois)
142                        throws IOException, ClassNotFoundException {
143
144                Object obj = ois.readObject();
145                if (!(obj instanceof RemoteInvocation)) {
146                        throw new RemoteException("Deserialized object needs to be assignable to type [" +
147                                        RemoteInvocation.class.getName() + "]: " + ClassUtils.getDescriptiveType(obj));
148                }
149                return (RemoteInvocation) obj;
150        }
151
152        /**
153         * Create an ObjectOutputStream for the given OutputStream.
154         * <p>The default implementation creates a plain
155         * {@link java.io.ObjectOutputStream}.
156         * @param os the OutputStream to write to
157         * @return the new ObjectOutputStream instance to use
158         * @throws java.io.IOException if creation of the ObjectOutputStream failed
159         */
160        protected ObjectOutputStream createObjectOutputStream(OutputStream os) throws IOException {
161                return new ObjectOutputStream(os);
162        }
163
164        /**
165         * Perform the actual writing of the given invocation result object
166         * to the given ObjectOutputStream.
167         * <p>The default implementation simply calls
168         * {@link java.io.ObjectOutputStream#writeObject}.
169         * Can be overridden for serialization of a custom wrapper object rather
170         * than the plain invocation, for example an encryption-aware holder.
171         * @param result the RemoteInvocationResult object
172         * @param oos the ObjectOutputStream to write to
173         * @throws java.io.IOException if thrown by I/O methods
174         */
175        protected void doWriteRemoteInvocationResult(RemoteInvocationResult result, ObjectOutputStream oos)
176                        throws IOException {
177
178                oos.writeObject(result);
179        }
180
181}