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}