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