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