001: /*
002: * Copyright 2002-2006 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.ejb.access;
018:
019: import java.lang.reflect.InvocationTargetException;
020: import java.rmi.RemoteException;
021:
022: import javax.ejb.EJBObject;
023: import javax.naming.NamingException;
024: import javax.rmi.PortableRemoteObject;
025:
026: import org.aopalliance.intercept.MethodInvocation;
027:
028: import org.springframework.remoting.RemoteConnectFailureException;
029: import org.springframework.remoting.RemoteLookupFailureException;
030: import org.springframework.remoting.rmi.RmiClientInterceptorUtils;
031:
032: /**
033: * Superclass for interceptors proxying remote Stateless Session Beans.
034: *
035: * <p>Such an interceptor must be the last interceptor in the advice chain.
036: * In this case, there is no target object.
037: *
038: * @author Rod Johnson
039: * @author Juergen Hoeller
040: */
041: public abstract class AbstractRemoteSlsbInvokerInterceptor extends
042: AbstractSlsbInvokerInterceptor {
043:
044: private Class homeInterface;
045:
046: private boolean refreshHomeOnConnectFailure = false;
047:
048: /**
049: * Set a home interface that this invoker will narrow to before performing
050: * the parameterless SLSB <code>create()</code> call that returns the actual
051: * SLSB proxy.
052: * <p>Default is none, which will work on all J2EE servers that are not based
053: * on CORBA. A plain <code>javax.ejb.EJBHome</code> interface is known to be
054: * sufficient to make a WebSphere 5.0 Remote SLSB work. On other servers,
055: * the specific home interface for the target SLSB might be necessary.
056: */
057: public void setHomeInterface(Class homeInterface) {
058: if (homeInterface != null && !homeInterface.isInterface()) {
059: throw new IllegalArgumentException("Home interface class ["
060: + homeInterface.getClass()
061: + "] is not an interface");
062: }
063: this .homeInterface = homeInterface;
064: }
065:
066: /**
067: * Set whether to refresh the EJB home on connect failure.
068: * Default is "false".
069: * <p>Can be turned on to allow for hot restart of the EJB server.
070: * If a cached EJB home throws an RMI exception that indicates a
071: * remote connect failure, a fresh home will be fetched and the
072: * invocation will be retried.
073: * @see java.rmi.ConnectException
074: * @see java.rmi.ConnectIOException
075: * @see java.rmi.NoSuchObjectException
076: */
077: public void setRefreshHomeOnConnectFailure(
078: boolean refreshHomeOnConnectFailure) {
079: this .refreshHomeOnConnectFailure = refreshHomeOnConnectFailure;
080: }
081:
082: protected boolean isHomeRefreshable() {
083: return this .refreshHomeOnConnectFailure;
084: }
085:
086: /**
087: * This overridden lookup implementation performs a narrow operation
088: * after the JNDI lookup, provided that a home interface is specified.
089: * @see #setHomeInterface
090: * @see javax.rmi.PortableRemoteObject#narrow
091: */
092: protected Object lookup() throws NamingException {
093: Object homeObject = super .lookup();
094: if (this .homeInterface != null) {
095: try {
096: homeObject = PortableRemoteObject.narrow(homeObject,
097: this .homeInterface);
098: } catch (ClassCastException ex) {
099: throw new RemoteLookupFailureException(
100: "Could not narrow EJB home stub to home interface ["
101: + this .homeInterface.getName() + "]",
102: ex);
103: }
104: }
105: return homeObject;
106: }
107:
108: /**
109: * Fetches an EJB home object and delegates to doInvoke.
110: * If configured to refresh on connect failure, it will call
111: * refreshAndRetry on corresponding RMI exceptions.
112: * @see #getHome
113: * @see #doInvoke
114: * @see #refreshAndRetry
115: * @see java.rmi.ConnectException
116: * @see java.rmi.ConnectIOException
117: * @see java.rmi.NoSuchObjectException
118: */
119: public Object invoke(MethodInvocation invocation) throws Throwable {
120: try {
121: return doInvoke(invocation);
122: } catch (RemoteConnectFailureException ex) {
123: return handleRemoteConnectFailure(invocation, ex);
124: } catch (RemoteException ex) {
125: if (isConnectFailure(ex)) {
126: return handleRemoteConnectFailure(invocation, ex);
127: } else {
128: throw ex;
129: }
130: }
131: }
132:
133: /**
134: * Determine whether the given RMI exception indicates a connect failure.
135: * Default implementation delegates to RmiClientInterceptorUtils.
136: * @param ex the RMI exception to check
137: * @return whether the exception should be treated as connect failure
138: * @see org.springframework.remoting.rmi.RmiClientInterceptorUtils#isConnectFailure
139: */
140: protected boolean isConnectFailure(RemoteException ex) {
141: return RmiClientInterceptorUtils.isConnectFailure(ex);
142: }
143:
144: private Object handleRemoteConnectFailure(
145: MethodInvocation invocation, Exception ex) throws Throwable {
146: if (this .refreshHomeOnConnectFailure) {
147: if (logger.isDebugEnabled()) {
148: logger.debug("Could not connect to remote EJB ["
149: + getJndiName() + "] - retrying", ex);
150: } else if (logger.isWarnEnabled()) {
151: logger.warn("Could not connect to remote EJB ["
152: + getJndiName() + "] - retrying");
153: }
154: return refreshAndRetry(invocation);
155: } else {
156: throw ex;
157: }
158: }
159:
160: /**
161: * Refresh the EJB home object and retry the given invocation.
162: * Called by invoke on connect failure.
163: * @param invocation the AOP method invocation
164: * @return the invocation result, if any
165: * @throws Throwable in case of invocation failure
166: * @see #invoke
167: */
168: protected Object refreshAndRetry(MethodInvocation invocation)
169: throws Throwable {
170: try {
171: refreshHome();
172: } catch (NamingException ex) {
173: throw new RemoteLookupFailureException(
174: "Failed to locate remote EJB [" + getJndiName()
175: + "]", ex);
176: }
177: return doInvoke(invocation);
178: }
179:
180: /**
181: * Perform the given invocation on the current EJB home.
182: * Template method to be implemented by a subclass.
183: * @param invocation the AOP method invocation
184: * @return the invocation result, if any
185: * @throws Throwable in case of invocation failure
186: * @see #getHome
187: * @see #newSessionBeanInstance
188: */
189: protected abstract Object doInvoke(MethodInvocation invocation)
190: throws Throwable;
191:
192: /**
193: * Return a new instance of the stateless session bean.
194: * To be invoked by concrete remote SLSB invoker subclasses.
195: * <p>Can be overridden to change the algorithm.
196: * @throws NamingException if thrown by JNDI
197: * @throws InvocationTargetException if thrown by the create method
198: * @see #create
199: */
200: protected EJBObject newSessionBeanInstance()
201: throws NamingException, InvocationTargetException {
202: if (logger.isDebugEnabled()) {
203: logger.debug("Trying to create reference to remote EJB");
204: }
205:
206: // invoke the superclass' generic create method
207: Object ejbInstance = create();
208: if (!(ejbInstance instanceof EJBObject)) {
209: throw new RemoteLookupFailureException("EJB instance ["
210: + ejbInstance
211: + "] is not a Remote Stateless Session Bean");
212: }
213: // if it throws remote exception (wrapped in InvocationTargetException), retry?
214:
215: if (logger.isDebugEnabled()) {
216: logger.debug("Obtained reference to remote EJB: "
217: + ejbInstance);
218: }
219: return (EJBObject) ejbInstance;
220: }
221:
222: /**
223: * Remove the given EJB instance.
224: * To be invoked by concrete remote SLSB invoker subclasses.
225: * @param ejb the EJB instance to remove
226: * @see javax.ejb.EJBObject#remove
227: */
228: protected void removeSessionBeanInstance(EJBObject ejb) {
229: if (ejb != null) {
230: try {
231: ejb.remove();
232: } catch (Throwable ex) {
233: logger
234: .warn(
235: "Could not invoke 'remove' on remote EJB proxy",
236: ex);
237: }
238: }
239: }
240:
241: }
|