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}