001/*
002 * Copyright 2002-2016 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.caucho;
018
019import java.io.BufferedInputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.OutputStream;
023import java.io.PrintWriter;
024
025import com.caucho.hessian.io.AbstractHessianInput;
026import com.caucho.hessian.io.AbstractHessianOutput;
027import com.caucho.hessian.io.Hessian2Input;
028import com.caucho.hessian.io.Hessian2Output;
029import com.caucho.hessian.io.HessianDebugInputStream;
030import com.caucho.hessian.io.HessianDebugOutputStream;
031import com.caucho.hessian.io.HessianInput;
032import com.caucho.hessian.io.HessianOutput;
033import com.caucho.hessian.io.HessianRemoteResolver;
034import com.caucho.hessian.io.SerializerFactory;
035import com.caucho.hessian.server.HessianSkeleton;
036import org.apache.commons.logging.Log;
037
038import org.springframework.beans.factory.InitializingBean;
039import org.springframework.remoting.support.RemoteExporter;
040import org.springframework.util.Assert;
041import org.springframework.util.CommonsLogWriter;
042
043/**
044 * General stream-based protocol exporter for a Hessian endpoint.
045 *
046 * <p>Hessian is a slim, binary RPC protocol.
047 * For information on Hessian, see the
048 * <a href="http://hessian.caucho.com">Hessian website</a>.
049 * <b>Note: As of Spring 4.0, this exporter requires Hessian 4.0 or above.</b>
050 *
051 * @author Juergen Hoeller
052 * @since 2.5.1
053 * @see #invoke(java.io.InputStream, java.io.OutputStream)
054 * @see HessianServiceExporter
055 * @see SimpleHessianServiceExporter
056 */
057public class HessianExporter extends RemoteExporter implements InitializingBean {
058
059        public static final String CONTENT_TYPE_HESSIAN = "application/x-hessian";
060
061
062        private SerializerFactory serializerFactory = new SerializerFactory();
063
064        private HessianRemoteResolver remoteResolver;
065
066        private Log debugLogger;
067
068        private HessianSkeleton skeleton;
069
070
071        /**
072         * Specify the Hessian SerializerFactory to use.
073         * <p>This will typically be passed in as an inner bean definition
074         * of type {@code com.caucho.hessian.io.SerializerFactory},
075         * with custom bean property values applied.
076         */
077        public void setSerializerFactory(SerializerFactory serializerFactory) {
078                this.serializerFactory = (serializerFactory != null ? serializerFactory : new SerializerFactory());
079        }
080
081        /**
082         * Set whether to send the Java collection type for each serialized
083         * collection. Default is "true".
084         */
085        public void setSendCollectionType(boolean sendCollectionType) {
086                this.serializerFactory.setSendCollectionType(sendCollectionType);
087        }
088
089        /**
090         * Set whether to allow non-serializable types as Hessian arguments
091         * and return values. Default is "true".
092         */
093        public void setAllowNonSerializable(boolean allowNonSerializable) {
094                this.serializerFactory.setAllowNonSerializable(allowNonSerializable);
095        }
096
097        /**
098         * Specify a custom HessianRemoteResolver to use for resolving remote
099         * object references.
100         */
101        public void setRemoteResolver(HessianRemoteResolver remoteResolver) {
102                this.remoteResolver = remoteResolver;
103        }
104
105        /**
106         * Set whether Hessian's debug mode should be enabled, logging to
107         * this exporter's Commons Logging log. Default is "false".
108         * @see com.caucho.hessian.client.HessianProxyFactory#setDebug
109         */
110        public void setDebug(boolean debug) {
111                this.debugLogger = (debug ? logger : null);
112        }
113
114
115        @Override
116        public void afterPropertiesSet() {
117                prepare();
118        }
119
120        /**
121         * Initialize this exporter.
122         */
123        public void prepare() {
124                checkService();
125                checkServiceInterface();
126                this.skeleton = new HessianSkeleton(getProxyForService(), getServiceInterface());
127        }
128
129
130        /**
131         * Perform an invocation on the exported object.
132         * @param inputStream the request stream
133         * @param outputStream the response stream
134         * @throws Throwable if invocation failed
135         */
136        public void invoke(InputStream inputStream, OutputStream outputStream) throws Throwable {
137                Assert.notNull(this.skeleton, "Hessian exporter has not been initialized");
138                doInvoke(this.skeleton, inputStream, outputStream);
139        }
140
141        /**
142         * Actually invoke the skeleton with the given streams.
143         * @param skeleton the skeleton to invoke
144         * @param inputStream the request stream
145         * @param outputStream the response stream
146         * @throws Throwable if invocation failed
147         */
148        protected void doInvoke(HessianSkeleton skeleton, InputStream inputStream, OutputStream outputStream)
149                        throws Throwable {
150
151                ClassLoader originalClassLoader = overrideThreadContextClassLoader();
152                try {
153                        InputStream isToUse = inputStream;
154                        OutputStream osToUse = outputStream;
155
156                        if (this.debugLogger != null && this.debugLogger.isDebugEnabled()) {
157                                PrintWriter debugWriter = new PrintWriter(new CommonsLogWriter(this.debugLogger));
158                                @SuppressWarnings("resource")
159                                HessianDebugInputStream dis = new HessianDebugInputStream(inputStream, debugWriter);
160                                @SuppressWarnings("resource")
161                                HessianDebugOutputStream dos = new HessianDebugOutputStream(outputStream, debugWriter);
162                                dis.startTop2();
163                                dos.startTop2();
164                                isToUse = dis;
165                                osToUse = dos;
166                        }
167
168                        if (!isToUse.markSupported()) {
169                                isToUse = new BufferedInputStream(isToUse);
170                                isToUse.mark(1);
171                        }
172
173                        int code = isToUse.read();
174                        int major;
175                        int minor;
176
177                        AbstractHessianInput in;
178                        AbstractHessianOutput out;
179
180                        if (code == 'H') {
181                                // Hessian 2.0 stream
182                                major = isToUse.read();
183                                minor = isToUse.read();
184                                if (major != 0x02) {
185                                        throw new IOException("Version " + major + '.' + minor + " is not understood");
186                                }
187                                in = new Hessian2Input(isToUse);
188                                out = new Hessian2Output(osToUse);
189                                in.readCall();
190                        }
191                        else if (code == 'C') {
192                                // Hessian 2.0 call... for some reason not handled in HessianServlet!
193                                isToUse.reset();
194                                in = new Hessian2Input(isToUse);
195                                out = new Hessian2Output(osToUse);
196                                in.readCall();
197                        }
198                        else if (code == 'c') {
199                                // Hessian 1.0 call
200                                major = isToUse.read();
201                                minor = isToUse.read();
202                                in = new HessianInput(isToUse);
203                                if (major >= 2) {
204                                        out = new Hessian2Output(osToUse);
205                                }
206                                else {
207                                        out = new HessianOutput(osToUse);
208                                }
209                        }
210                        else {
211                                throw new IOException("Expected 'H'/'C' (Hessian 2.0) or 'c' (Hessian 1.0) in hessian input at " + code);
212                        }
213
214                        if (this.serializerFactory != null) {
215                                in.setSerializerFactory(this.serializerFactory);
216                                out.setSerializerFactory(this.serializerFactory);
217                        }
218                        if (this.remoteResolver != null) {
219                                in.setRemoteResolver(this.remoteResolver);
220                        }
221
222                        try {
223                                skeleton.invoke(in, out);
224                        }
225                        finally {
226                                try {
227                                        in.close();
228                                        isToUse.close();
229                                }
230                                catch (IOException ex) {
231                                        // ignore
232                                }
233                                try {
234                                        out.close();
235                                        osToUse.close();
236                                }
237                                catch (IOException ex) {
238                                        // ignore
239                                }
240                        }
241                }
242                finally {
243                        resetThreadContextClassLoader(originalClassLoader);
244                }
245        }
246
247}