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.jaxrpc;
018:
019: import java.lang.reflect.InvocationTargetException;
020: import java.rmi.Remote;
021: import java.rmi.RemoteException;
022: import java.util.HashMap;
023: import java.util.Iterator;
024: import java.util.Map;
025: import java.util.Properties;
026:
027: import javax.xml.namespace.QName;
028: import javax.xml.rpc.Call;
029: import javax.xml.rpc.JAXRPCException;
030: import javax.xml.rpc.Service;
031: import javax.xml.rpc.ServiceException;
032: import javax.xml.rpc.Stub;
033:
034: import org.aopalliance.intercept.MethodInterceptor;
035: import org.aopalliance.intercept.MethodInvocation;
036:
037: import org.springframework.aop.support.AopUtils;
038: import org.springframework.beans.factory.InitializingBean;
039: import org.springframework.remoting.RemoteLookupFailureException;
040: import org.springframework.remoting.RemoteProxyFailureException;
041: import org.springframework.remoting.rmi.RmiClientInterceptorUtils;
042: import org.springframework.util.CollectionUtils;
043:
044: /**
045: * Interceptor for accessing a specific port of a JAX-RPC service.
046: * Uses either {@link LocalJaxRpcServiceFactory}'s facilities underneath,
047: * or takes an explicit reference to an existing JAX-RPC Service instance
048: * (e.g. obtained via {@link org.springframework.jndi.JndiObjectFactoryBean}).
049: *
050: * <p>Allows to set JAX-RPC's standard stub properties directly, via the
051: * "username", "password", "endpointAddress" and "maintainSession" properties.
052: * For typical usage, it is not necessary to specify those.
053: *
054: * <p>In standard JAX-RPC style, this invoker is used with an RMI service interface.
055: * Alternatively, this invoker can also proxy a JAX-RPC service with a matching
056: * non-RMI business interface, that is, an interface that declares the service methods
057: * without RemoteExceptions. In the latter case, RemoteExceptions thrown by JAX-RPC
058: * will automatically get converted to Spring's unchecked RemoteAccessException.
059: *
060: * <p>Setting "serviceInterface" is usually sufficient: The invoker will automatically
061: * use JAX-RPC "dynamic invocations" via the Call API in this case, no matter whether
062: * the specified interface is an RMI or non-RMI interface. Alternatively, a corresponding
063: * JAX-RPC port interface can be specified as "portInterface", which will turn this
064: * invoker into "static invocation" mode (operating on a standard JAX-RPC port stub).
065: *
066: * @author Juergen Hoeller
067: * @since 15.12.2003
068: * @see #setPortName
069: * @see #setServiceInterface
070: * @see #setPortInterface
071: * @see javax.xml.rpc.Service#createCall
072: * @see javax.xml.rpc.Service#getPort
073: * @see org.springframework.remoting.RemoteAccessException
074: * @see org.springframework.jndi.JndiObjectFactoryBean
075: */
076: public class JaxRpcPortClientInterceptor extends
077: LocalJaxRpcServiceFactory implements MethodInterceptor,
078: InitializingBean {
079:
080: private Service jaxRpcService;
081:
082: private Service serviceToUse;
083:
084: private String portName;
085:
086: private String username;
087:
088: private String password;
089:
090: private String endpointAddress;
091:
092: private boolean maintainSession;
093:
094: /** Map of custom properties, keyed by property name (String) */
095: private final Map customPropertyMap = new HashMap();
096:
097: private Class serviceInterface;
098:
099: private Class portInterface;
100:
101: private boolean lookupServiceOnStartup = true;
102:
103: private boolean refreshServiceAfterConnectFailure = false;
104:
105: private QName portQName;
106:
107: private Remote portStub;
108:
109: private final Object preparationMonitor = new Object();
110:
111: /**
112: * Set a reference to an existing JAX-RPC Service instance,
113: * for example obtained via {@link org.springframework.jndi.JndiObjectFactoryBean}.
114: * If not set, LocalJaxRpcServiceFactory's properties have to be specified.
115: * @see #setServiceFactoryClass
116: * @see #setWsdlDocumentUrl
117: * @see #setNamespaceUri
118: * @see #setServiceName
119: * @see org.springframework.jndi.JndiObjectFactoryBean
120: */
121: public void setJaxRpcService(Service jaxRpcService) {
122: this .jaxRpcService = jaxRpcService;
123: }
124:
125: /**
126: * Return a reference to an existing JAX-RPC Service instance, if any.
127: */
128: public Service getJaxRpcService() {
129: return this .jaxRpcService;
130: }
131:
132: /**
133: * Set the name of the port.
134: * Corresponds to the "wsdl:port" name.
135: */
136: public void setPortName(String portName) {
137: this .portName = portName;
138: }
139:
140: /**
141: * Return the name of the port.
142: */
143: public String getPortName() {
144: return this .portName;
145: }
146:
147: /**
148: * Set the username to specify on the stub or call.
149: * @see javax.xml.rpc.Stub#USERNAME_PROPERTY
150: * @see javax.xml.rpc.Call#USERNAME_PROPERTY
151: */
152: public void setUsername(String username) {
153: this .username = username;
154: }
155:
156: /**
157: * Return the username to specify on the stub or call.
158: */
159: public String getUsername() {
160: return this .username;
161: }
162:
163: /**
164: * Set the password to specify on the stub or call.
165: * @see javax.xml.rpc.Stub#PASSWORD_PROPERTY
166: * @see javax.xml.rpc.Call#PASSWORD_PROPERTY
167: */
168: public void setPassword(String password) {
169: this .password = password;
170: }
171:
172: /**
173: * Return the password to specify on the stub or call.
174: */
175: public String getPassword() {
176: return this .password;
177: }
178:
179: /**
180: * Set the endpoint address to specify on the stub or call.
181: * @see javax.xml.rpc.Stub#ENDPOINT_ADDRESS_PROPERTY
182: * @see javax.xml.rpc.Call#setTargetEndpointAddress
183: */
184: public void setEndpointAddress(String endpointAddress) {
185: this .endpointAddress = endpointAddress;
186: }
187:
188: /**
189: * Return the endpoint address to specify on the stub or call.
190: */
191: public String getEndpointAddress() {
192: return this .endpointAddress;
193: }
194:
195: /**
196: * Set the maintain session flag to specify on the stub or call.
197: * @see javax.xml.rpc.Stub#SESSION_MAINTAIN_PROPERTY
198: * @see javax.xml.rpc.Call#SESSION_MAINTAIN_PROPERTY
199: */
200: public void setMaintainSession(boolean maintainSession) {
201: this .maintainSession = maintainSession;
202: }
203:
204: /**
205: * Return the maintain session flag to specify on the stub or call.
206: */
207: public boolean isMaintainSession() {
208: return this .maintainSession;
209: }
210:
211: /**
212: * Set custom properties to be set on the stub or call.
213: * <p>Can be populated with a String "value" (parsed via PropertiesEditor)
214: * or a "props" element in XML bean definitions.
215: * @see javax.xml.rpc.Stub#_setProperty
216: * @see javax.xml.rpc.Call#setProperty
217: */
218: public void setCustomProperties(Properties customProperties) {
219: CollectionUtils.mergePropertiesIntoMap(customProperties,
220: this .customPropertyMap);
221: }
222:
223: /**
224: * Set custom properties to be set on the stub or call.
225: * <p>Can be populated with a "map" or "props" element in XML bean definitions.
226: * @see javax.xml.rpc.Stub#_setProperty
227: * @see javax.xml.rpc.Call#setProperty
228: */
229: public void setCustomPropertyMap(Map customProperties) {
230: if (customProperties != null) {
231: Iterator it = customProperties.entrySet().iterator();
232: while (it.hasNext()) {
233: Map.Entry entry = (Map.Entry) it.next();
234: if (!(entry.getKey() instanceof String)) {
235: throw new IllegalArgumentException(
236: "Illegal property key [" + entry.getKey()
237: + "]: only Strings allowed");
238: }
239: addCustomProperty((String) entry.getKey(), entry
240: .getValue());
241: }
242: }
243: }
244:
245: /**
246: * Allow Map access to the custom properties to be set on the stub
247: * or call, with the option to add or override specific entries.
248: * <p>Useful for specifying entries directly, for example via
249: * "customPropertyMap[myKey]". This is particularly useful for
250: * adding or overriding entries in child bean definitions.
251: */
252: public Map getCustomPropertyMap() {
253: return this .customPropertyMap;
254: }
255:
256: /**
257: * Add a custom property to this JAX-RPC Stub/Call.
258: * @param name the name of the attribute to expose
259: * @param value the attribute value to expose
260: * @see javax.xml.rpc.Stub#_setProperty
261: * @see javax.xml.rpc.Call#setProperty
262: */
263: public void addCustomProperty(String name, Object value) {
264: this .customPropertyMap.put(name, value);
265: }
266:
267: /**
268: * Set the interface of the service that this factory should create a proxy for.
269: * This will typically be a non-RMI business interface, although you can also
270: * use an RMI port interface as recommended by JAX-RPC here.
271: * <p>Calls on the specified service interface will either be translated to the
272: * underlying RMI port interface (in case of a "portInterface" being specified)
273: * or to dynamic calls (using the JAX-RPC Dynamic Invocation Interface).
274: * <p>The dynamic call mechanism has the advantage that you don't need to
275: * maintain an RMI port interface in addition to an existing non-RMI business
276: * interface. In terms of configuration, specifying the business interface
277: * as "serviceInterface" will be enough; this interceptor will automatically
278: * use dynamic calls in such a scenario.
279: * @see javax.xml.rpc.Service#createCall
280: * @see #setPortInterface
281: */
282: public void setServiceInterface(Class serviceInterface) {
283: if (serviceInterface != null && !serviceInterface.isInterface()) {
284: throw new IllegalArgumentException(
285: "serviceInterface must be an interface");
286: }
287: this .serviceInterface = serviceInterface;
288: }
289:
290: /**
291: * Return the interface of the service that this factory should create a proxy for.
292: */
293: public Class getServiceInterface() {
294: return this .serviceInterface;
295: }
296:
297: /**
298: * Set the JAX-RPC port interface to use. Only needs to be set if a JAX-RPC
299: * port stub should be used instead of the dynamic call mechanism.
300: * See the javadoc of the "serviceInterface" property for more details.
301: * <p>The interface must be suitable for a JAX-RPC port, that is, it must be
302: * an RMI service interface (that extends <code>java.rmi.Remote</code>).
303: * <p><b>NOTE:</b> Check whether your JAX-RPC provider returns thread-safe
304: * port stubs. If not, use the dynamic call mechanism instead, which will
305: * always be thread-safe. In particular, do not use JAX-RPC port stubs
306: * with Apache Axis, whose port stubs are known to be non-thread-safe.
307: * @see javax.xml.rpc.Service#getPort
308: * @see java.rmi.Remote
309: * @see #setServiceInterface
310: */
311: public void setPortInterface(Class portInterface) {
312: if (portInterface != null
313: && (!portInterface.isInterface() || !Remote.class
314: .isAssignableFrom(portInterface))) {
315: throw new IllegalArgumentException(
316: "portInterface must be an interface derived from [java.rmi.Remote]");
317: }
318: this .portInterface = portInterface;
319: }
320:
321: /**
322: * Return the JAX-RPC port interface to use.
323: */
324: public Class getPortInterface() {
325: return this .portInterface;
326: }
327:
328: /**
329: * Set whether to look up the JAX-RPC service on startup.
330: * <p>Default is "true". Turn this flag off to allow for late start
331: * of the target server. In this case, the JAX-RPC service will be
332: * lazily fetched on first access.
333: */
334: public void setLookupServiceOnStartup(boolean lookupServiceOnStartup) {
335: this .lookupServiceOnStartup = lookupServiceOnStartup;
336: }
337:
338: /**
339: * Set whether to refresh the JAX-RPC service on connect failure,
340: * that is, whenever a JAX-RPC invocation throws a RemoteException.
341: * <p>Default is "false", keeping a reference to the JAX-RPC service
342: * in any case, retrying the next invocation on the same service
343: * even in case of failure. Turn this flag on to reinitialize the
344: * entire service in case of connect failures.
345: */
346: public void setRefreshServiceAfterConnectFailure(
347: boolean refreshServiceAfterConnectFailure) {
348: this .refreshServiceAfterConnectFailure = refreshServiceAfterConnectFailure;
349: }
350:
351: /**
352: * Prepares the JAX-RPC service and port if the "lookupServiceOnStartup"
353: * is turned on (which it is by default).
354: */
355: public void afterPropertiesSet() throws ServiceException {
356: if (this .lookupServiceOnStartup) {
357: prepare();
358: }
359: }
360:
361: /**
362: * Create and initialize the JAX-RPC service for the specified port.
363: * <p>Prepares a JAX-RPC stub if possible (if an RMI interface is available);
364: * falls back to JAX-RPC dynamic calls else. Using dynamic calls can be
365: * enforced through overriding <code>alwaysUseJaxRpcCall</code> to return true.
366: * <p><code>postProcessJaxRpcService</code> and <code>postProcessPortStub</code>
367: * hooks are available for customization in subclasses. When using dynamic calls,
368: * each can be post-processed via <code>postProcessJaxRpcCall</code>.
369: * <p>Note: As of Spring 2.1, this method will always throw
370: * RemoteLookupFailureException and not declare ServiceException anymore.
371: * @throws ServiceException in case of service initialization failure
372: * @throws RemoteLookupFailureException if port stub creation failed
373: * @see #alwaysUseJaxRpcCall
374: * @see #postProcessJaxRpcService
375: * @see #postProcessPortStub
376: * @see #postProcessJaxRpcCall
377: */
378: public void prepare() throws ServiceException,
379: RemoteLookupFailureException {
380: if (getPortName() == null) {
381: throw new IllegalArgumentException(
382: "Property 'portName' is required");
383: }
384:
385: synchronized (this .preparationMonitor) {
386: this .serviceToUse = null;
387:
388: // Cache the QName for the port.
389: this .portQName = getQName(getPortName());
390:
391: Service service = getJaxRpcService();
392: if (service == null) {
393: service = createJaxRpcService();
394: } else {
395: postProcessJaxRpcService(service);
396: }
397:
398: Class portInterface = getPortInterface();
399: if (portInterface != null && !alwaysUseJaxRpcCall()) {
400: // JAX-RPC-compliant port interface -> using JAX-RPC stub for port.
401:
402: if (logger.isDebugEnabled()) {
403: logger
404: .debug("Creating JAX-RPC proxy for JAX-RPC port ["
405: + this .portQName
406: + "], using port interface ["
407: + portInterface.getName() + "]");
408: }
409: Remote remoteObj = service.getPort(this .portQName,
410: portInterface);
411:
412: if (logger.isDebugEnabled()) {
413: Class serviceInterface = getServiceInterface();
414: if (serviceInterface != null) {
415: boolean isImpl = serviceInterface
416: .isInstance(remoteObj);
417: logger.debug("Using service interface ["
418: + serviceInterface.getName()
419: + "] for JAX-RPC port ["
420: + this .portQName + "] - "
421: + (!isImpl ? "not" : "")
422: + " directly implemented");
423: }
424: }
425:
426: if (!(remoteObj instanceof Stub)) {
427: throw new RemoteLookupFailureException(
428: "Port stub of class ["
429: + remoteObj.getClass().getName()
430: + "] is not a valid JAX-RPC stub: it does not implement interface [javax.xml.rpc.Stub]");
431: }
432: Stub stub = (Stub) remoteObj;
433:
434: // Apply properties to JAX-RPC stub.
435: preparePortStub(stub);
436:
437: // Allow for custom post-processing in subclasses.
438: postProcessPortStub(stub);
439:
440: this .portStub = remoteObj;
441: }
442:
443: else {
444: // No JAX-RPC-compliant port interface -> using JAX-RPC dynamic calls.
445: if (logger.isDebugEnabled()) {
446: logger
447: .debug("Using JAX-RPC dynamic calls for JAX-RPC port ["
448: + this .portQName + "]");
449: }
450: }
451:
452: this .serviceToUse = service;
453: }
454: }
455:
456: /**
457: * Return whether to always use JAX-RPC dynamic calls.
458: * Called by <code>afterPropertiesSet</code>.
459: * <p>Default is "false"; if an RMI interface is specified as "portInterface"
460: * or "serviceInterface", it will be used to create a JAX-RPC port stub.
461: * <p>Can be overridden to enforce the use of the JAX-RPC Call API,
462: * for example if there is a need to customize at the Call level.
463: * This just necessary if you you want to use an RMI interface as
464: * "serviceInterface", though; in case of only a non-RMI interface being
465: * available, this interceptor will fall back to the Call API anyway.
466: * @see #postProcessJaxRpcCall
467: */
468: protected boolean alwaysUseJaxRpcCall() {
469: return false;
470: }
471:
472: /**
473: * Reset the prepared service of this interceptor,
474: * allowing for reinitialization on next access.
475: */
476: protected void reset() {
477: synchronized (this .preparationMonitor) {
478: this .serviceToUse = null;
479: }
480: }
481:
482: /**
483: * Return whether this client interceptor has already been prepared,
484: * i.e. has already looked up the JAX-RPC service and port.
485: */
486: protected boolean isPrepared() {
487: synchronized (this .preparationMonitor) {
488: return (this .serviceToUse != null);
489: }
490: }
491:
492: /**
493: * Return the prepared QName for the port.
494: * @see #setPortName
495: * @see #getQName
496: */
497: protected QName getPortQName() {
498: return this .portQName;
499: }
500:
501: /**
502: * Prepare the given JAX-RPC port stub, applying properties to it.
503: * Called by {@link #afterPropertiesSet}.
504: * <p>Just applied when actually creating a JAX-RPC port stub, in case of a
505: * compliant port interface. Else, JAX-RPC dynamic calls will be used.
506: * @param stub the current JAX-RPC port stub
507: * @see #setUsername
508: * @see #setPassword
509: * @see #setEndpointAddress
510: * @see #setMaintainSession
511: * @see #setCustomProperties
512: * @see #setPortInterface
513: * @see #prepareJaxRpcCall
514: */
515: protected void preparePortStub(Stub stub) {
516: String username = getUsername();
517: if (username != null) {
518: stub._setProperty(Stub.USERNAME_PROPERTY, username);
519: }
520: String password = getPassword();
521: if (password != null) {
522: stub._setProperty(Stub.PASSWORD_PROPERTY, password);
523: }
524: String endpointAddress = getEndpointAddress();
525: if (endpointAddress != null) {
526: stub._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY,
527: endpointAddress);
528: }
529: if (isMaintainSession()) {
530: stub._setProperty(Stub.SESSION_MAINTAIN_PROPERTY,
531: Boolean.TRUE);
532: }
533: if (this .customPropertyMap != null) {
534: for (Iterator it = this .customPropertyMap.keySet()
535: .iterator(); it.hasNext();) {
536: String key = (String) it.next();
537: stub._setProperty(key, this .customPropertyMap.get(key));
538: }
539: }
540: }
541:
542: /**
543: * Post-process the given JAX-RPC port stub. Called by {@link #prepare}.
544: * <p>The default implementation is empty.
545: * <p>Just applied when actually creating a JAX-RPC port stub, in case of a
546: * compliant port interface. Else, JAX-RPC dynamic calls will be used.
547: * @param stub the current JAX-RPC port stub
548: * (can be cast to an implementation-specific class if necessary)
549: * @see #setPortInterface
550: * @see #postProcessJaxRpcCall
551: */
552: protected void postProcessPortStub(Stub stub) {
553: }
554:
555: /**
556: * Return the underlying JAX-RPC port stub that this interceptor delegates to
557: * for each method invocation on the proxy.
558: */
559: protected Remote getPortStub() {
560: return this .portStub;
561: }
562:
563: /**
564: * Translates the method invocation into a JAX-RPC service invocation.
565: * <p>Prepares the service on the fly, if necessary, in case of lazy
566: * lookup or a connect failure having happened.
567: * @see #prepare()
568: * @see #doInvoke
569: */
570: public Object invoke(MethodInvocation invocation) throws Throwable {
571: if (AopUtils.isToStringMethod(invocation.getMethod())) {
572: return "JAX-RPC proxy for port [" + getPortName()
573: + "] of service [" + getServiceName() + "]";
574: }
575:
576: // Lazily prepare service and stub if appropriate.
577: if (!this .lookupServiceOnStartup
578: || this .refreshServiceAfterConnectFailure) {
579: synchronized (this .preparationMonitor) {
580: if (!isPrepared()) {
581: try {
582: prepare();
583: } catch (ServiceException ex) {
584: throw new RemoteLookupFailureException(
585: "Preparation of JAX-RPC service failed",
586: ex);
587: }
588: }
589: }
590: } else {
591: if (!isPrepared()) {
592: throw new IllegalStateException(
593: "JaxRpcClientInterceptor is not properly initialized - "
594: + "invoke 'prepare' before attempting any operations");
595: }
596: }
597:
598: return doInvoke(invocation);
599: }
600:
601: /**
602: * Perform a JAX-RPC service invocation based on the given method invocation.
603: * <p>Uses traditional RMI stub invocation if a JAX-RPC port stub is available;
604: * falls back to JAX-RPC dynamic calls else.
605: * @param invocation the AOP method invocation
606: * @return the invocation result, if any
607: * @throws Throwable in case of invocation failure
608: * @see #getPortStub()
609: * @see #doInvoke(org.aopalliance.intercept.MethodInvocation, java.rmi.Remote)
610: * @see #performJaxRpcCall(org.aopalliance.intercept.MethodInvocation, javax.xml.rpc.Service)
611: */
612: protected Object doInvoke(MethodInvocation invocation)
613: throws Throwable {
614: Remote stub = getPortStub();
615: if (stub != null) {
616: // JAX-RPC port stub available -> traditional RMI stub invocation.
617: if (logger.isTraceEnabled()) {
618: logger.trace("Invoking operation '"
619: + invocation.getMethod().getName()
620: + "' on JAX-RPC port stub");
621: }
622: return doInvoke(invocation, stub);
623: } else {
624: // No JAX-RPC stub -> using JAX-RPC dynamic calls.
625: if (logger.isTraceEnabled()) {
626: logger.trace("Invoking operation '"
627: + invocation.getMethod().getName()
628: + "' as JAX-RPC dynamic call");
629: }
630: return performJaxRpcCall(invocation);
631: }
632: }
633:
634: /**
635: * Perform a JAX-RPC service invocation based on the given port stub.
636: * @param invocation the AOP method invocation
637: * @param portStub the RMI port stub to invoke
638: * @return the invocation result, if any
639: * @throws Throwable in case of invocation failure
640: * @see #getPortStub()
641: * @see #doInvoke(org.aopalliance.intercept.MethodInvocation, java.rmi.Remote)
642: * @see #performJaxRpcCall
643: */
644: protected Object doInvoke(MethodInvocation invocation,
645: Remote portStub) throws Throwable {
646: try {
647: return RmiClientInterceptorUtils.doInvoke(invocation,
648: portStub);
649: } catch (InvocationTargetException ex) {
650: Throwable targetEx = ex.getTargetException();
651: if (targetEx instanceof RemoteException) {
652: RemoteException rex = (RemoteException) targetEx;
653: boolean isConnectFailure = isConnectFailure(rex);
654: if (isConnectFailure
655: && this .refreshServiceAfterConnectFailure) {
656: reset();
657: }
658: throw RmiClientInterceptorUtils
659: .convertRmiAccessException(invocation
660: .getMethod(), rex, isConnectFailure,
661: this .portQName.toString());
662: } else if (targetEx instanceof JAXRPCException) {
663: throw new RemoteProxyFailureException(
664: "Invalid call on JAX-RPC port stub", targetEx);
665: } else {
666: throw targetEx;
667: }
668: }
669: }
670:
671: /**
672: * @deprecated as of Spring 2.0.3, in favor of the <code>performJaxRpcCall</code>
673: * variant with an explicit Service argument
674: * @see #performJaxRpcCall(org.aopalliance.intercept.MethodInvocation, javax.xml.rpc.Service)
675: */
676: protected Object performJaxRpcCall(MethodInvocation invocation)
677: throws Throwable {
678: return performJaxRpcCall(invocation, this .serviceToUse);
679: }
680:
681: /**
682: * Perform a JAX-RPC dynamic call for the given AOP method invocation.
683: * Delegates to {@link #prepareJaxRpcCall} and
684: * {@link #postProcessJaxRpcCall} for setting up the call object.
685: * <p>Default implementation uses method name as JAX-RPC operation name
686: * and method arguments as arguments for the JAX-RPC call. Can be
687: * overridden in subclasses for custom operation names and/or arguments.
688: * @param invocation the current AOP MethodInvocation that should
689: * be converted to a JAX-RPC call
690: * @return the return value of the invocation, if any
691: * @throws Throwable the exception thrown by the invocation, if any
692: * @see #getPortQName
693: * @see #prepareJaxRpcCall
694: * @see #postProcessJaxRpcCall
695: */
696: protected Object performJaxRpcCall(MethodInvocation invocation,
697: Service service) throws Throwable {
698: QName portQName = getPortQName();
699:
700: // Create JAX-RPC call object, using the method name as operation name.
701: // Synchronized because of non-thread-safe Axis implementation!
702: Call call = null;
703: synchronized (service) {
704: call = service.createCall(portQName, invocation.getMethod()
705: .getName());
706: }
707:
708: // Apply properties to JAX-RPC stub.
709: prepareJaxRpcCall(call);
710:
711: // Allow for custom post-processing in subclasses.
712: postProcessJaxRpcCall(call, invocation);
713:
714: // Perform actual invocation.
715: try {
716: return call.invoke(invocation.getArguments());
717: } catch (RemoteException ex) {
718: boolean isConnectFailure = isConnectFailure(ex);
719: if (isConnectFailure
720: && this .refreshServiceAfterConnectFailure) {
721: reset();
722: }
723: throw RmiClientInterceptorUtils.convertRmiAccessException(
724: invocation.getMethod(), ex, isConnectFailure,
725: portQName.toString());
726: } catch (JAXRPCException ex) {
727: throw new RemoteProxyFailureException(
728: "Invalid JAX-RPC call configuration", ex);
729: }
730: }
731:
732: /**
733: * Prepare the given JAX-RPC call, applying properties to it. Called by {@link #invoke}.
734: * <p>Just applied when actually using JAX-RPC dynamic calls, i.e. if no compliant
735: * port interface was specified. Else, a JAX-RPC port stub will be used.
736: * @param call the current JAX-RPC call object
737: * @see #setUsername
738: * @see #setPassword
739: * @see #setEndpointAddress
740: * @see #setMaintainSession
741: * @see #setCustomProperties
742: * @see #setPortInterface
743: * @see #preparePortStub
744: */
745: protected void prepareJaxRpcCall(Call call) {
746: String username = getUsername();
747: if (username != null) {
748: call.setProperty(Call.USERNAME_PROPERTY, username);
749: }
750: String password = getPassword();
751: if (password != null) {
752: call.setProperty(Call.PASSWORD_PROPERTY, password);
753: }
754: String endpointAddress = getEndpointAddress();
755: if (endpointAddress != null) {
756: call.setTargetEndpointAddress(endpointAddress);
757: }
758: if (isMaintainSession()) {
759: call.setProperty(Call.SESSION_MAINTAIN_PROPERTY,
760: Boolean.TRUE);
761: }
762: if (this .customPropertyMap != null) {
763: for (Iterator it = this .customPropertyMap.keySet()
764: .iterator(); it.hasNext();) {
765: String key = (String) it.next();
766: call.setProperty(key, this .customPropertyMap.get(key));
767: }
768: }
769: }
770:
771: /**
772: * Post-process the given JAX-RPC call. Called by {@link #invoke}.
773: * <p>The default implementation is empty.
774: * <p>Just applied when actually using JAX-RPC dynamic calls, i.e. if no compliant
775: * port interface was specified. Else, a JAX-RPC port stub will be used.
776: * @param call the current JAX-RPC call object
777: * (can be cast to an implementation-specific class if necessary)
778: * @param invocation the current AOP MethodInvocation that the call was
779: * created for (can be used to check method name, method parameters
780: * and/or passed-in arguments)
781: * @see #setPortInterface
782: * @see #postProcessPortStub
783: */
784: protected void postProcessJaxRpcCall(Call call,
785: MethodInvocation invocation) {
786: }
787:
788: /**
789: * Determine whether the given RMI exception indicates a connect failure.
790: * <p>The default implementation always returns <code>true</code>,
791: * assuming that the JAX-RPC provider only throws RemoteException
792: * in case of connect failures.
793: * @param ex the RMI exception to check
794: * @return whether the exception should be treated as connect failure
795: * @see org.springframework.remoting.rmi.RmiClientInterceptorUtils#isConnectFailure
796: */
797: protected boolean isConnectFailure(RemoteException ex) {
798: return true;
799: }
800:
801: }
|