001/* 002 * Copyright 2002-2013 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.lang.reflect.InvocationTargetException; 020import java.lang.reflect.UndeclaredThrowableException; 021import java.net.ConnectException; 022import java.net.MalformedURLException; 023 024import com.caucho.hessian.HessianException; 025import com.caucho.hessian.client.HessianConnectionException; 026import com.caucho.hessian.client.HessianConnectionFactory; 027import com.caucho.hessian.client.HessianProxyFactory; 028import com.caucho.hessian.client.HessianRuntimeException; 029import com.caucho.hessian.io.SerializerFactory; 030import org.aopalliance.intercept.MethodInterceptor; 031import org.aopalliance.intercept.MethodInvocation; 032 033import org.springframework.remoting.RemoteAccessException; 034import org.springframework.remoting.RemoteConnectFailureException; 035import org.springframework.remoting.RemoteLookupFailureException; 036import org.springframework.remoting.RemoteProxyFailureException; 037import org.springframework.remoting.support.UrlBasedRemoteAccessor; 038import org.springframework.util.Assert; 039 040/** 041 * {@link org.aopalliance.intercept.MethodInterceptor} for accessing a Hessian service. 042 * Supports authentication via username and password. 043 * The service URL must be an HTTP URL exposing a Hessian service. 044 * 045 * <p>Hessian is a slim, binary RPC protocol. 046 * For information on Hessian, see the 047 * <a href="http://hessian.caucho.com">Hessian website</a> 048 * <b>Note: As of Spring 4.0, this client requires Hessian 4.0 or above.</b> 049 * 050 * <p>Note: There is no requirement for services accessed with this proxy factory 051 * to have been exported using Spring's {@link HessianServiceExporter}, as there is 052 * no special handling involved. As a consequence, you can also access services that 053 * have been exported using Caucho's {@link com.caucho.hessian.server.HessianServlet}. 054 * 055 * @author Juergen Hoeller 056 * @since 29.09.2003 057 * @see #setServiceInterface 058 * @see #setServiceUrl 059 * @see #setUsername 060 * @see #setPassword 061 * @see HessianServiceExporter 062 * @see HessianProxyFactoryBean 063 * @see com.caucho.hessian.client.HessianProxyFactory 064 * @see com.caucho.hessian.server.HessianServlet 065 */ 066public class HessianClientInterceptor extends UrlBasedRemoteAccessor implements MethodInterceptor { 067 068 private HessianProxyFactory proxyFactory = new HessianProxyFactory(); 069 070 private Object hessianProxy; 071 072 073 /** 074 * Set the HessianProxyFactory instance to use. 075 * If not specified, a default HessianProxyFactory will be created. 076 * <p>Allows to use an externally configured factory instance, 077 * in particular a custom HessianProxyFactory subclass. 078 */ 079 public void setProxyFactory(HessianProxyFactory proxyFactory) { 080 this.proxyFactory = (proxyFactory != null ? proxyFactory : new HessianProxyFactory()); 081 } 082 083 /** 084 * Specify the Hessian SerializerFactory to use. 085 * <p>This will typically be passed in as an inner bean definition 086 * of type {@code com.caucho.hessian.io.SerializerFactory}, 087 * with custom bean property values applied. 088 */ 089 public void setSerializerFactory(SerializerFactory serializerFactory) { 090 this.proxyFactory.setSerializerFactory(serializerFactory); 091 } 092 093 /** 094 * Set whether to send the Java collection type for each serialized 095 * collection. Default is "true". 096 */ 097 public void setSendCollectionType(boolean sendCollectionType) { 098 this.proxyFactory.getSerializerFactory().setSendCollectionType(sendCollectionType); 099 } 100 101 /** 102 * Set whether to allow non-serializable types as Hessian arguments 103 * and return values. Default is "true". 104 */ 105 public void setAllowNonSerializable(boolean allowNonSerializable) { 106 this.proxyFactory.getSerializerFactory().setAllowNonSerializable(allowNonSerializable); 107 } 108 109 /** 110 * Set whether overloaded methods should be enabled for remote invocations. 111 * Default is "false". 112 * @see com.caucho.hessian.client.HessianProxyFactory#setOverloadEnabled 113 */ 114 public void setOverloadEnabled(boolean overloadEnabled) { 115 this.proxyFactory.setOverloadEnabled(overloadEnabled); 116 } 117 118 /** 119 * Set the username that this factory should use to access the remote service. 120 * Default is none. 121 * <p>The username will be sent by Hessian via HTTP Basic Authentication. 122 * @see com.caucho.hessian.client.HessianProxyFactory#setUser 123 */ 124 public void setUsername(String username) { 125 this.proxyFactory.setUser(username); 126 } 127 128 /** 129 * Set the password that this factory should use to access the remote service. 130 * Default is none. 131 * <p>The password will be sent by Hessian via HTTP Basic Authentication. 132 * @see com.caucho.hessian.client.HessianProxyFactory#setPassword 133 */ 134 public void setPassword(String password) { 135 this.proxyFactory.setPassword(password); 136 } 137 138 /** 139 * Set whether Hessian's debug mode should be enabled. 140 * Default is "false". 141 * @see com.caucho.hessian.client.HessianProxyFactory#setDebug 142 */ 143 public void setDebug(boolean debug) { 144 this.proxyFactory.setDebug(debug); 145 } 146 147 /** 148 * Set whether to use a chunked post for sending a Hessian request. 149 * @see com.caucho.hessian.client.HessianProxyFactory#setChunkedPost 150 */ 151 public void setChunkedPost(boolean chunkedPost) { 152 this.proxyFactory.setChunkedPost(chunkedPost); 153 } 154 155 /** 156 * Specify a custom HessianConnectionFactory to use for the Hessian client. 157 */ 158 public void setConnectionFactory(HessianConnectionFactory connectionFactory) { 159 this.proxyFactory.setConnectionFactory(connectionFactory); 160 } 161 162 /** 163 * Set the socket connect timeout to use for the Hessian client. 164 * @see com.caucho.hessian.client.HessianProxyFactory#setConnectTimeout 165 */ 166 public void setConnectTimeout(long timeout) { 167 this.proxyFactory.setConnectTimeout(timeout); 168 } 169 170 /** 171 * Set the timeout to use when waiting for a reply from the Hessian service. 172 * @see com.caucho.hessian.client.HessianProxyFactory#setReadTimeout 173 */ 174 public void setReadTimeout(long timeout) { 175 this.proxyFactory.setReadTimeout(timeout); 176 } 177 178 /** 179 * Set whether version 2 of the Hessian protocol should be used for 180 * parsing requests and replies. Default is "false". 181 * @see com.caucho.hessian.client.HessianProxyFactory#setHessian2Request 182 */ 183 public void setHessian2(boolean hessian2) { 184 this.proxyFactory.setHessian2Request(hessian2); 185 this.proxyFactory.setHessian2Reply(hessian2); 186 } 187 188 /** 189 * Set whether version 2 of the Hessian protocol should be used for 190 * parsing requests. Default is "false". 191 * @see com.caucho.hessian.client.HessianProxyFactory#setHessian2Request 192 */ 193 public void setHessian2Request(boolean hessian2) { 194 this.proxyFactory.setHessian2Request(hessian2); 195 } 196 197 /** 198 * Set whether version 2 of the Hessian protocol should be used for 199 * parsing replies. Default is "false". 200 * @see com.caucho.hessian.client.HessianProxyFactory#setHessian2Reply 201 */ 202 public void setHessian2Reply(boolean hessian2) { 203 this.proxyFactory.setHessian2Reply(hessian2); 204 } 205 206 207 @Override 208 public void afterPropertiesSet() { 209 super.afterPropertiesSet(); 210 prepare(); 211 } 212 213 /** 214 * Initialize the Hessian proxy for this interceptor. 215 * @throws RemoteLookupFailureException if the service URL is invalid 216 */ 217 public void prepare() throws RemoteLookupFailureException { 218 try { 219 this.hessianProxy = createHessianProxy(this.proxyFactory); 220 } 221 catch (MalformedURLException ex) { 222 throw new RemoteLookupFailureException("Service URL [" + getServiceUrl() + "] is invalid", ex); 223 } 224 } 225 226 /** 227 * Create the Hessian proxy that is wrapped by this interceptor. 228 * @param proxyFactory the proxy factory to use 229 * @return the Hessian proxy 230 * @throws MalformedURLException if thrown by the proxy factory 231 * @see com.caucho.hessian.client.HessianProxyFactory#create 232 */ 233 protected Object createHessianProxy(HessianProxyFactory proxyFactory) throws MalformedURLException { 234 Assert.notNull(getServiceInterface(), "'serviceInterface' is required"); 235 return proxyFactory.create(getServiceInterface(), getServiceUrl(), getBeanClassLoader()); 236 } 237 238 239 @Override 240 public Object invoke(MethodInvocation invocation) throws Throwable { 241 if (this.hessianProxy == null) { 242 throw new IllegalStateException("HessianClientInterceptor is not properly initialized - " + 243 "invoke 'prepare' before attempting any operations"); 244 } 245 246 ClassLoader originalClassLoader = overrideThreadContextClassLoader(); 247 try { 248 return invocation.getMethod().invoke(this.hessianProxy, invocation.getArguments()); 249 } 250 catch (InvocationTargetException ex) { 251 Throwable targetEx = ex.getTargetException(); 252 // Hessian 4.0 check: another layer of InvocationTargetException. 253 if (targetEx instanceof InvocationTargetException) { 254 targetEx = ((InvocationTargetException) targetEx).getTargetException(); 255 } 256 if (targetEx instanceof HessianConnectionException) { 257 throw convertHessianAccessException(targetEx); 258 } 259 else if (targetEx instanceof HessianException || targetEx instanceof HessianRuntimeException) { 260 Throwable cause = targetEx.getCause(); 261 throw convertHessianAccessException(cause != null ? cause : targetEx); 262 } 263 else if (targetEx instanceof UndeclaredThrowableException) { 264 UndeclaredThrowableException utex = (UndeclaredThrowableException) targetEx; 265 throw convertHessianAccessException(utex.getUndeclaredThrowable()); 266 } 267 else { 268 throw targetEx; 269 } 270 } 271 catch (Throwable ex) { 272 throw new RemoteProxyFailureException( 273 "Failed to invoke Hessian proxy for remote service [" + getServiceUrl() + "]", ex); 274 } 275 finally { 276 resetThreadContextClassLoader(originalClassLoader); 277 } 278 } 279 280 /** 281 * Convert the given Hessian access exception to an appropriate 282 * Spring RemoteAccessException. 283 * @param ex the exception to convert 284 * @return the RemoteAccessException to throw 285 */ 286 protected RemoteAccessException convertHessianAccessException(Throwable ex) { 287 if (ex instanceof HessianConnectionException || ex instanceof ConnectException) { 288 return new RemoteConnectFailureException( 289 "Cannot connect to Hessian remote service at [" + getServiceUrl() + "]", ex); 290 } 291 else { 292 return new RemoteAccessException( 293 "Cannot access Hessian remote service at [" + getServiceUrl() + "]", ex); 294 } 295 } 296 297}