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