001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.proxy.ejb;
023:
024: import java.io.ObjectOutput;
025: import java.io.IOException;
026: import java.io.ObjectInput;
027: import java.util.Hashtable;
028: import java.util.Properties;
029: import javax.naming.InitialContext;
030:
031: import org.jboss.invocation.Invocation;
032: import org.jboss.invocation.InvocationContext;
033: import org.jboss.invocation.InvocationType;
034: import org.jboss.invocation.InvocationKey;
035: import org.jboss.invocation.Invoker;
036: import org.jboss.invocation.ServiceUnavailableException;
037: import org.jboss.logging.Logger;
038: import org.jboss.naming.NamingContextFactory;
039: import org.jboss.proxy.Interceptor;
040:
041: /** An interceptor that will retry failed invocations by restoring the
042: * InvocationContext invoker. This is triggered by a ServiceUnavailableException
043: * which causes the interceptor to fall into a while loop that retries the
044: * lookup of the transport invoker using the jndi name obtained from the
045: * invocation context under the key InvocationKey.JNDI_NAME, with the additional
046: * extension of "-RemoteInvoker" if the invocation type is InvocationType.REMOTE
047: * and "-HomeInvoker" if the invocation type is InvocationType.HOME.
048: *
049: * The JNDI environment used for the lookup can be set via the setRetryEnv.
050: * Typically this is an HA-JNDI configuration with one or more bootstrap
051: * urls. If not set, an attempt will be made to use
052: * {@link NamingContextFactory#getInitialContext(Hashtable)} to find the
053: * JNDI environment. This will only be useful if java.naming.factory.initial
054: * was set to org.jboss.naming.NamingContextFactory. If neither of the above
055: * steps yield a set of naming environment properties, a default InitialContext
056: * will be used.
057: *
058: * @author Scott.Stark@jboss.org
059: * @author brian.stansberry@jboss.org
060: *
061: * @version $Revision: 57209 $
062: */
063: public class RetryInterceptor extends Interceptor {
064: /** Serial Version Identifier. @since 1.0 */
065: private static final long serialVersionUID = 1;
066: /** The current externalized data version */
067: private static final int EXTERNAL_VERSION = 1;
068: private static Logger log = Logger
069: .getLogger(RetryInterceptor.class);
070: /** The HA-JNDI environment used to restore the invoker proxy */
071: private static Properties retryEnv;
072:
073: /** A flag that can be set to abort the retry loop */
074: private transient boolean retry;
075: /** The logging trace flag */
076: private transient boolean trace;
077: /** Max number of retries. -1 means retry until sucessful */
078: private transient int maxRetries = -1;
079: /** Number of ms to sleep before each attempt to reestablish the invoker */
080: private transient long sleepTime = 1000;
081:
082: /**
083: * Set the HA-JNDI InitialContext env used to lookup the invoker proxy
084: * @param env the InitialContext env used to lookup the invoker proxy
085: */
086: public static void setRetryEnv(Properties env) {
087: retryEnv = env;
088: }
089:
090: /**
091: * No-argument constructor for externalization.
092: */
093: public RetryInterceptor() {
094: }
095:
096: /**
097: * Create a new RetryInterceptor that will retry the specified
098: * number of times.
099: *
100: * @param maxRetries the maximum number of retries to attempt. -1
101: * (the default) means retry until successful.
102: * @param sleepTime number of ms to pause between each retry attempt
103: */
104: protected RetryInterceptor(int maxRetries, long sleepTime) {
105: this .maxRetries = maxRetries;
106: this .sleepTime = sleepTime;
107: }
108:
109: // Public --------------------------------------------------------
110:
111: public void setRetry(boolean flag) {
112: this .retry = flag;
113: }
114:
115: public boolean getRetry() {
116: return this .retry;
117: }
118:
119: /**
120: * Gets the maximum number of retries that will be attempted.
121: */
122: public int getMaxRetries() {
123: return maxRetries;
124: }
125:
126: /**
127: * Sets the maximum number of retries that will be attempted.
128: *
129: * @param maxRetries the maximum number of retries to attempt. -1
130: * (the default) means retry until successful.
131: */
132: public void setMaxRetries(int maxRetries) {
133: this .maxRetries = maxRetries;
134: }
135:
136: /**
137: * Gets the number of ms of sleep between each retry attempt.
138: */
139: public long getSleepTime() {
140: return sleepTime;
141: }
142:
143: /**
144: * Sets the number of ms of sleep between each retry attempt.
145: */
146: public void setSleepTime(long sleepTime) {
147: this .sleepTime = sleepTime;
148: }
149:
150: /**
151: * InvocationHandler implementation.
152: *
153: * @throws Throwable Any exception or error thrown while processing.
154: */
155: public Object invoke(Invocation invocation) throws Throwable {
156: Object result = null;
157: InvocationContext ctx = invocation.getInvocationContext();
158: retry = true;
159: int retryCount = 0;
160: while (retry == true) {
161: Interceptor next = getNext();
162: try {
163: if (trace)
164: log.trace("invoke, method="
165: + invocation.getMethod());
166: result = next.invoke(invocation);
167: break;
168: } catch (ServiceUnavailableException e) {
169: if (trace)
170: log.trace("Invocation failed", e);
171:
172: InvocationType type = invocation.getType();
173: if ((maxRetries > -1 && retryCount >= maxRetries)
174: || reestablishInvokerProxy(ctx, type) == false) {
175: throw e;
176: }
177: retryCount++;
178: }
179: }
180: return result;
181: }
182:
183: /**
184: * Loop trying to lookup the proxy invoker from jndi. Continue trying until
185: * successful or {@link #getMaxRetries() maxRetries} attempts have been made
186: * without success. This sleeps 1 second between lookup operations.
187: *
188: * @param ctx - the invocation context to populate with the new invoker
189: * @param type - the type of the invocation, InvocationType.REMOTE or
190: * InvocationType.HOME
191: *
192: * @return <code>true</code> if a lookup was successful, <code>false</code>
193: * if {@link #getMaxRetries() maxRetries} attempts were made
194: * without success.
195: */
196: private boolean reestablishInvokerProxy(InvocationContext ctx,
197: InvocationType type) {
198: if (trace)
199: log.trace("Begin reestablishInvokerProxy");
200:
201: boolean isRemote = type == InvocationType.REMOTE;
202: String jndiName = (String) ctx
203: .getValue(InvocationKey.JNDI_NAME);
204: if (isRemote == true)
205: jndiName += "-RemoteInvoker";
206: else
207: jndiName += "-HomeInvoker";
208: Hashtable retryProps = retryEnv;
209: if (retryProps == null) {
210: retryProps = (Hashtable) NamingContextFactory.lastInitialContextEnv
211: .get();
212: if (trace) {
213: if (retryProps != null)
214: log
215: .trace("Using retry properties from NamingContextFactory");
216: else
217: log.trace("No retry properties available");
218: }
219: } else if (trace) {
220: log.trace("Using static retry properties");
221: }
222:
223: int retryCount = 0;
224: Invoker newInvoker = null;
225: while (retry == true) {
226: try {
227: Thread.sleep(sleepTime);
228: InitialContext namingCtx = new InitialContext(
229: retryProps);
230: if (trace)
231: log.trace("Looking for invoker: " + jndiName);
232: newInvoker = (Invoker) namingCtx.lookup(jndiName);
233: if (trace)
234: log.trace("Found invoker: " + newInvoker);
235: ctx.setInvoker(newInvoker);
236: break;
237: } catch (Throwable t) {
238: retryCount++;
239: if (trace)
240: log.trace("Retry attempt " + retryCount
241: + ": Failed to lookup proxy", t);
242: if (maxRetries > -1 && retryCount >= maxRetries) {
243: if (trace)
244: log.trace("Maximum retry attempts made");
245: break;
246: }
247: }
248: }
249: if (trace)
250: log.trace("End reestablishInvokerProxy");
251:
252: return (newInvoker != null);
253: }
254:
255: /**
256: * Writes the next interceptor.
257: */
258: public void writeExternal(final ObjectOutput out)
259: throws IOException {
260: super .writeExternal(out);
261: // Write out a version identifier for future extensibility
262: out.writeInt(EXTERNAL_VERSION);
263: // There is no additional data currently
264: }
265:
266: /**
267: * Reads the next interceptor.
268: */
269: public void readExternal(final ObjectInput in) throws IOException,
270: ClassNotFoundException {
271: super .readExternal(in);
272: // Read the version identifier
273: int version = in.readInt();
274: if (version == EXTERNAL_VERSION) {
275: // This version has no additional data
276: }
277: // Set the logging trace level
278: trace = log.isTraceEnabled();
279: }
280: }
|