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