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}