View Javadoc

1   /*
2    * $Id: WorkerContext.java 19191 2010-08-25 21:05:23Z tcarlson $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  /**
12   *
13   * Copyright 2004 The Apache Software Foundation
14   *
15   *  Licensed under the Apache License, Version 2.0 (the "License");
16   *  you may not use this file except in compliance with the License.
17   *  You may obtain a copy of the License at
18   *
19   *     http://www.apache.org/licenses/LICENSE-2.0
20   *
21   *  Unless required by applicable law or agreed to in writing, software
22   *  distributed under the License is distributed on an "AS IS" BASIS,
23   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24   *  See the License for the specific language governing permissions and
25   *  limitations under the License.
26   */
27  
28  package org.mule.work;
29  
30  import org.mule.util.concurrent.Latch;
31  
32  import javax.resource.spi.work.ExecutionContext;
33  import javax.resource.spi.work.Work;
34  import javax.resource.spi.work.WorkAdapter;
35  import javax.resource.spi.work.WorkCompletedException;
36  import javax.resource.spi.work.WorkEvent;
37  import javax.resource.spi.work.WorkException;
38  import javax.resource.spi.work.WorkListener;
39  import javax.resource.spi.work.WorkRejectedException;
40  
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  
44  /**
45   * <code>WorkerContext</code> TODO
46   */
47  public class WorkerContext implements Work
48  {
49  
50      /**
51       * logger used by this class
52       */
53      protected static final Log logger = LogFactory.getLog(WorkerContext.class);
54  
55      /**
56       * Null WorkListener used as the default WorkListener.
57       */
58      private static final WorkListener NULL_WORK_LISTENER = new WorkAdapter()
59      {
60          @Override
61          public void workRejected(WorkEvent event)
62          {
63              if (event.getException() != null)
64              {
65                  if (event.getException() instanceof WorkCompletedException
66                      && event.getException().getCause() != null)
67                  {
68                      logger.error(event.getWork().toString(), event.getException().getCause());
69                  }
70                  else
71                  {
72                      logger.error(event.getWork().toString(), event.getException());
73                  }
74              }
75          }
76      };
77  
78      protected ClassLoader executionClassLoader;
79  
80      /**
81       * Priority of the thread, which will execute this work.
82       */
83      private int threadPriority;
84  
85      /**
86       * Actual work to be executed.
87       */
88      private Work worker;
89  
90      /**
91       * System.currentTimeMillis() when the wrapped Work has been accepted.
92       */
93      private long acceptedTime;
94  
95      /**
96       * Number of times that the execution of this work has been tried.
97       */
98      private int retryCount;
99  
100     /**
101      * Time duration (in milliseconds) within which the execution of the Work
102      * instance must start.
103      */
104     private long startTimeOut;
105 
106     /**
107      * Execution context of the actual work to be executed.
108      */
109     private final ExecutionContext executionContext;
110 
111     /**
112      * Listener to be notified during the life-cycle of the work treatment.
113      */
114     private WorkListener workListener = NULL_WORK_LISTENER;
115 
116     /**
117      * Work exception, if any.
118      */
119     private WorkException workException;
120 
121     /**
122      * A latch, which is released when the work is started.
123      */
124     private final Latch startLatch = new Latch();
125 
126     /**
127      * A latch, which is released when the work is completed.
128      */
129     private final Latch endLatch = new Latch();
130 
131     {
132         this.executionClassLoader = Thread.currentThread().getContextClassLoader();
133     }
134 
135     /**
136      * Create a WorkWrapper.
137      * 
138      * @param work Work to be wrapped.
139      */
140     public WorkerContext(Work work)
141     {
142         worker = work;
143         executionContext = null;
144     }
145 
146     /**
147      * Create a WorkWrapper with the specified execution context.
148      * 
149      * @param aWork Work to be wrapped.
150      * @param aStartTimeout a time duration (in milliseconds) within which the
151      *            execution of the Work instance must start.
152      * @param execContext an object containing the execution context with which the
153      *            submitted Work instance must be executed.
154      * @param workListener an object which would be notified when the various Work
155      *            processing events (work accepted, work rejected, work started,
156      */
157     public WorkerContext(Work aWork,
158                          long aStartTimeout,
159                          ExecutionContext execContext,
160                          WorkListener workListener)
161     {
162         worker = aWork;
163         startTimeOut = aStartTimeout;
164         executionContext = execContext;
165         if (null != workListener)
166         {
167             this.workListener = workListener;
168         }
169     }
170 
171     public void release()
172     {
173         worker.release();
174     }
175 
176     /**
177      * Defines the thread priority level of the thread, which will be dispatched to
178      * process this work. This priority level must be the same one for a given
179      * resource adapter.
180      * 
181      * @param aPriority Priority of the thread to be used to process the wrapped Work
182      *            instance.
183      */
184     public void setThreadPriority(int aPriority)
185     {
186         threadPriority = aPriority;
187     }
188 
189     /**
190      * Gets the priority level of the thread, which will be dispatched to process
191      * this work. This priority level must be the same one for a given resource
192      * adapter.
193      * 
194      * @return The priority level of the thread to be dispatched to process the
195      *         wrapped Work instance.
196      */
197     public int getThreadPriority()
198     {
199         return threadPriority;
200     }
201 
202     /**
203      * Call-back method used by a Work executor in order to notify this instance that
204      * the wrapped Work instance has been accepted.
205      * 
206      * @param anObject Object on which the event initially occurred. It should be the
207      *            work executor.
208      */
209     public synchronized void workAccepted(Object anObject)
210     {
211         acceptedTime = System.currentTimeMillis();
212         workListener.workAccepted(new WorkEvent(anObject, WorkEvent.WORK_ACCEPTED, worker, null));
213     }
214 
215     /**
216      * System.currentTimeMillis() when the Work has been accepted. This method can be
217      * used to compute the duration of a work.
218      * 
219      * @return When the work has been accepted.
220      */
221     public synchronized long getAcceptedTime()
222     {
223         return acceptedTime;
224     }
225 
226     /**
227      * Gets the time duration (in milliseconds) within which the execution of the
228      * Work instance must start.
229      * 
230      * @return Time out duration.
231      */
232     public long getStartTimeout()
233     {
234         return startTimeOut;
235     }
236 
237     /**
238      * Used by a Work executor in order to know if this work, which should be
239      * accepted but not started has timed out. This method MUST be called prior to
240      * retry the execution of a Work.
241      * 
242      * @return true if the Work has timed out and false otherwise.
243      */
244     public synchronized boolean isTimedOut()
245     {
246         // A value of 0 means that the work never times out.
247         // ??? really?
248         if (0 == startTimeOut || startTimeOut == MuleWorkManager.INDEFINITE)
249         {
250             return false;
251         }
252         boolean isTimeout = acceptedTime + startTimeOut > 0
253                             && System.currentTimeMillis() > acceptedTime + startTimeOut;
254         if (logger.isDebugEnabled())
255         {
256             logger.debug(this + " accepted at " + acceptedTime
257                          + (isTimeout ? " has timed out." : " has not timed out. ") + retryCount
258                          + " retries have been performed.");
259         }
260         if (isTimeout)
261         {
262             workException = new WorkRejectedException(this + " has timed out.", WorkException.START_TIMED_OUT);
263             workListener.workRejected(new WorkEvent(this, WorkEvent.WORK_REJECTED, worker, workException));
264             return true;
265         }
266         retryCount++;
267         return isTimeout;
268     }
269 
270     /**
271      * Gets the WorkException, if any, thrown during the execution.
272      * 
273      * @return WorkException, if any.
274      */
275     public synchronized WorkException getWorkException()
276     {
277         return workException;
278     }
279 
280     public void run()
281     {
282         if (isTimedOut())
283         {
284             // In case of a time out, one releases the start and end latches
285             // to prevent a dead-lock.
286             startLatch.countDown();
287             endLatch.countDown();
288             return;
289         }
290         // Implementation note: the work listener is notified prior to release
291         // the start lock. This behavior is intentional and seems to be the
292         // more conservative.
293         workListener.workStarted(new WorkEvent(this, WorkEvent.WORK_STARTED, worker, null));
294         startLatch.countDown();
295         // Implementation note: we assume this is being called without an
296         // interesting TransactionContext,
297         // and ignore/replace whatever is associated with the current thread.
298         try
299         {
300             if (executionContext == null || executionContext.getXid() == null)
301             {
302                 // TODO currently unused, see below
303                 // ExecutionContext context = new ExecutionContext();
304                 final ClassLoader originalCl = Thread.currentThread().getContextClassLoader();
305                 try
306                 {
307                     // execute with the application-specific classloader in the context
308                     Thread.currentThread().setContextClassLoader(executionClassLoader);
309                     worker.run();
310                 }
311                 finally
312                 {
313                     Thread.currentThread().setContextClassLoader(originalCl);
314                     
315                     // ExecutionContext returningContext = new
316                     // ExecutionContext();
317                     // if (context != returningContext) {
318                     // throw new WorkCompletedException("Wrong
319                     // TransactionContext on return from work done");
320                     // }
321                 }
322                 // TODO should we commit the txContext to flush any leftover
323                 // state???
324             }
325             else
326             {
327                 // try {
328                 // long transactionTimeout =
329                 // executionContext.getDefaultTransactionTimeout();
330                 // //translate -1 value to 0 to indicate default transaction
331                 // timeout.
332                 // transactionContextManager.begin(executionContext.getXid(),
333                 // transactionTimeout == -1 ? 0 : transactionTimeout);
334                 // } catch (XAException e) {
335                 // throw new WorkCompletedException("Transaction import failed
336                 // for xid " + executionContext.getXid(),
337                 // WorkCompletedException.TX_RECREATE_FAILED).initCause(e);
338                 // } catch (InvalidTransactionException e) {
339                 // throw new WorkCompletedException("Transaction import failed
340                 // for xid " + executionContext.getXid(),
341                 // WorkCompletedException.TX_RECREATE_FAILED).initCause(e);
342                 // } catch (SystemException e) {
343                 // throw new WorkCompletedException("Transaction import failed
344                 // for xid " + executionContext.getXid(),
345                 // WorkCompletedException.TX_RECREATE_FAILED).initCause(e);
346                 // } catch (ImportedTransactionActiveException e) {
347                 // throw new WorkCompletedException("Transaction already active
348                 // for xid " + executionContext.getXid(),
349                 // WorkCompletedException.TX_CONCURRENT_WORK_DISALLOWED);
350                 // }
351                 try
352                 {
353                     worker.run();
354                 }
355                 finally
356                 {
357                     // transactionContextManager.end(executionContext.getXid());
358                 }
359 
360             }
361             workListener.workCompleted(new WorkEvent(this, WorkEvent.WORK_COMPLETED, worker, null));
362         }
363         catch (Throwable e)
364         {
365             workException = (WorkException)(e instanceof WorkCompletedException
366                             ? e : new WorkCompletedException("Unknown error",
367                                 WorkCompletedException.UNDEFINED).initCause(e));
368             workListener.workCompleted(new WorkEvent(this, WorkEvent.WORK_REJECTED, worker, workException));
369         }
370         finally
371         {
372             endLatch.countDown();
373         }
374     }
375 
376     /**
377      * Provides a latch, which can be used to wait the start of a work execution.
378      * 
379      * @return Latch that a caller can acquire to wait for the start of a work
380      *         execution.
381      */
382     public Latch provideStartLatch()
383     {
384         return startLatch;
385     }
386 
387     /**
388      * Provides a latch, which can be used to wait the end of a work execution.
389      * 
390      * @return Latch that a caller can acquire to wait for the end of a work
391      *         execution.
392      */
393     public Latch provideEndLatch()
394     {
395         return endLatch;
396     }
397 
398     @Override
399     public String toString()
400     {
401         return "Work: " + worker;
402     }
403 }