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