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 }