001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.remoting.httpinvoker;
018:
019: import java.io.IOException;
020: import java.net.ConnectException;
021:
022: import org.aopalliance.intercept.MethodInterceptor;
023: import org.aopalliance.intercept.MethodInvocation;
024:
025: import org.springframework.aop.support.AopUtils;
026: import org.springframework.beans.factory.BeanClassLoaderAware;
027: import org.springframework.remoting.RemoteAccessException;
028: import org.springframework.remoting.RemoteConnectFailureException;
029: import org.springframework.remoting.support.RemoteInvocation;
030: import org.springframework.remoting.support.RemoteInvocationBasedAccessor;
031: import org.springframework.remoting.support.RemoteInvocationResult;
032: import org.springframework.util.ClassUtils;
033:
034: /**
035: * Interceptor for accessing an HTTP invoker service.
036: * The service URL must be an HTTP URL exposing an HTTP invoker service.
037: *
038: * <p>Serializes remote invocation objects and deserializes remote invocation
039: * result objects. Uses Java serialization just like RMI, but provides the
040: * same ease of setup as Caucho's HTTP-based Hessian and Burlap protocols.
041: *
042: * <P>HTTP invoker is a very extensible and customizable protocol.
043: * It supports the RemoteInvocationFactory mechanism, like RMI invoker,
044: * allowing to include additional invocation attributes (for example,
045: * a security context). Furthermore, it allows to customize request
046: * execution via the {@link HttpInvokerRequestExecutor} strategy.
047: *
048: * <p>Can use the JDK's {@link java.rmi.server.RMIClassLoader} to load
049: * classes from a given {@link #setCodebaseUrl codebase}, performing
050: * on-demand dynamic code download from a remote location. The codebase
051: * can consist of multiple URLs, separated by spaces. Note that
052: * RMIClassLoader requires a SecurityManager to be set, analogous to
053: * when using dynamic class download with standard RMI!
054: * (See the RMI documentation for details.)
055: *
056: * @author Juergen Hoeller
057: * @since 1.1
058: * @see #setServiceUrl
059: * @see #setCodebaseUrl
060: * @see #setRemoteInvocationFactory
061: * @see #setHttpInvokerRequestExecutor
062: * @see HttpInvokerServiceExporter
063: * @see HttpInvokerProxyFactoryBean
064: * @see java.rmi.server.RMIClassLoader
065: */
066: public class HttpInvokerClientInterceptor extends
067: RemoteInvocationBasedAccessor implements MethodInterceptor,
068: HttpInvokerClientConfiguration, BeanClassLoaderAware {
069:
070: private String codebaseUrl;
071:
072: private HttpInvokerRequestExecutor httpInvokerRequestExecutor;
073:
074: private ClassLoader beanClassLoader = ClassUtils
075: .getDefaultClassLoader();
076:
077: /**
078: * Set the codebase URL to download classes from if not found locally.
079: * Can consists of multiple URLs, separated by spaces.
080: * <p>Follows RMI's codebase conventions for dynamic class download.
081: * In contrast to RMI, where the server determines the URL for class download
082: * (via the "java.rmi.server.codebase" system property), it's the client
083: * that determines the codebase URL here. The server will usually be the
084: * same as for the service URL, just pointing to a different path there.
085: * @see #setServiceUrl
086: * @see org.springframework.remoting.rmi.CodebaseAwareObjectInputStream
087: * @see java.rmi.server.RMIClassLoader
088: */
089: public void setCodebaseUrl(String codebaseUrl) {
090: this .codebaseUrl = codebaseUrl;
091: }
092:
093: /**
094: * Return the codebase URL to download classes from if not found locally.
095: */
096: public String getCodebaseUrl() {
097: return this .codebaseUrl;
098: }
099:
100: /**
101: * Set the HttpInvokerRequestExecutor implementation to use for executing
102: * remote invocations.
103: * <p>Default is {@link SimpleHttpInvokerRequestExecutor}. Alternatively,
104: * consider using {@link CommonsHttpInvokerRequestExecutor} for more
105: * sophisticated needs.
106: * @see SimpleHttpInvokerRequestExecutor
107: * @see CommonsHttpInvokerRequestExecutor
108: */
109: public void setHttpInvokerRequestExecutor(
110: HttpInvokerRequestExecutor httpInvokerRequestExecutor) {
111: this .httpInvokerRequestExecutor = httpInvokerRequestExecutor;
112: }
113:
114: /**
115: * Return the HttpInvokerRequestExecutor used by this remote accessor.
116: * <p>Creates a default SimpleHttpInvokerRequestExecutor if no executor
117: * has been initialized already.
118: */
119: public HttpInvokerRequestExecutor getHttpInvokerRequestExecutor() {
120: if (this .httpInvokerRequestExecutor == null) {
121: SimpleHttpInvokerRequestExecutor executor = new SimpleHttpInvokerRequestExecutor();
122: executor.setBeanClassLoader(this .beanClassLoader);
123: this .httpInvokerRequestExecutor = executor;
124: }
125: return this .httpInvokerRequestExecutor;
126: }
127:
128: public void setBeanClassLoader(ClassLoader classLoader) {
129: this .beanClassLoader = classLoader;
130: }
131:
132: /**
133: * Return the ClassLoader that this accessor operates in,
134: * to be used for deserializing and for generating proxies.
135: */
136: protected final ClassLoader getBeanClassLoader() {
137: return this .beanClassLoader;
138: }
139:
140: public void afterPropertiesSet() {
141: super .afterPropertiesSet();
142:
143: // Eagerly initialize the default HttpInvokerRequestExecutor, if needed.
144: getHttpInvokerRequestExecutor();
145: }
146:
147: public Object invoke(MethodInvocation methodInvocation)
148: throws Throwable {
149: if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
150: return "HTTP invoker proxy for service URL ["
151: + getServiceUrl() + "]";
152: }
153:
154: RemoteInvocation invocation = createRemoteInvocation(methodInvocation);
155: RemoteInvocationResult result = null;
156: try {
157: result = executeRequest(invocation);
158: } catch (Throwable ex) {
159: throw convertHttpInvokerAccessException(ex);
160: }
161: return recreateRemoteInvocationResult(result);
162: }
163:
164: /**
165: * Execute the given remote invocation via the HttpInvokerRequestExecutor.
166: * <p>Can be overridden in subclasses to pass a different configuration object
167: * to the executor. Alternatively, add further configuration properties in a
168: * subclass of this accessor: By default, the accessor passed itself as
169: * configuration object to the executor.
170: * @param invocation the RemoteInvocation to execute
171: * @return the RemoteInvocationResult object
172: * @throws IOException if thrown by I/O operations
173: * @throws ClassNotFoundException if thrown during deserialization
174: * @throws Exception in case of general errors
175: * @see #getHttpInvokerRequestExecutor
176: * @see HttpInvokerClientConfiguration
177: */
178: protected RemoteInvocationResult executeRequest(
179: RemoteInvocation invocation) throws Exception {
180: return getHttpInvokerRequestExecutor().executeRequest(this ,
181: invocation);
182: }
183:
184: /**
185: * Convert the given HTTP invoker access exception to an appropriate
186: * Spring RemoteAccessException.
187: * @param ex the exception to convert
188: * @return the RemoteAccessException to throw
189: */
190: protected RemoteAccessException convertHttpInvokerAccessException(
191: Throwable ex) {
192: if (ex instanceof ConnectException) {
193: throw new RemoteConnectFailureException(
194: "Cannot connect to HTTP invoker remote service at ["
195: + getServiceUrl() + "]", ex);
196: } else if (ex instanceof ClassNotFoundException) {
197: throw new RemoteAccessException(
198: "Cannot deserialize result from HTTP invoker remote service ["
199: + getServiceUrl() + "]", ex);
200: } else {
201: throw new RemoteAccessException(
202: "Cannot access HTTP invoker remote service at ["
203: + getServiceUrl() + "]", ex);
204: }
205: }
206:
207: }
|