/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.openejb.core.transaction; import org.apache.openejb.util.LogCategory; import org.apache.openejb.util.Logger; import javax.resource.spi.work.ExecutionContext; import javax.resource.spi.work.Work; import javax.resource.spi.work.WorkAdapter; import javax.resource.spi.work.WorkCompletedException; import javax.resource.spi.work.WorkEvent; import javax.resource.spi.work.WorkException; import javax.resource.spi.work.WorkListener; import javax.resource.spi.work.WorkManager; import javax.resource.spi.work.WorkRejectedException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; public class SimpleWorkManager implements WorkManager { public enum WorkType { DO, START, SCHEDULE } private static final Logger logger = Logger.getInstance(LogCategory.OPENEJB, SimpleWorkManager.class); /** * All work is performed by this executor */ private final Executor executor; public SimpleWorkManager(final Executor executor) { if (executor == null) { throw new NullPointerException("executor is null"); } this.executor = executor; } public void doWork(final Work work) throws WorkException { if (work == null) { throw new NullPointerException("work is null"); } doWork(work, INDEFINITE, null, null); } public void doWork(final Work work, final long startTimeout, final ExecutionContext executionContext, final WorkListener workListener) throws WorkException { if (work == null) { throw new NullPointerException("work is null"); } executeWork(WorkType.DO, work, startTimeout, executionContext, workListener); } public long startWork(final Work work) throws WorkException { if (work == null) { throw new NullPointerException("work is null"); } return startWork(work, INDEFINITE, null, null); } public long startWork(final Work work, final long startTimeout, final ExecutionContext executionContext, final WorkListener workListener) throws WorkException { if (work == null) { throw new NullPointerException("work is null"); } return executeWork(WorkType.START, work, startTimeout, executionContext, workListener); } public void scheduleWork(final Work work) throws WorkException { if (work == null) { throw new NullPointerException("work is null"); } scheduleWork(work, INDEFINITE, null, null); } public void scheduleWork(final Work work, final long startTimeout, final ExecutionContext executionContext, final WorkListener workListener) throws WorkException { if (work == null) { throw new NullPointerException("work is null"); } executeWork(WorkType.SCHEDULE, work, startTimeout, executionContext, workListener); } private long executeWork(final WorkType workType, final Work work, final long startTimeout, final ExecutionContext executionContext, WorkListener workListener) throws WorkException { // assure we have a work listener if (workListener == null) { workListener = new LoggingWorkListener(workType); } // reject work with an XID if (executionContext != null && executionContext.getXid() != null) { final WorkRejectedException workRejectedException = new WorkRejectedException("SimpleWorkManager can not import an XID", WorkException.TX_RECREATE_FAILED); workListener.workRejected(new WorkEvent(this, WorkEvent.WORK_REJECTED, work, workRejectedException)); throw workRejectedException; } // accecpt all other work workListener.workAccepted(new WorkEvent(this, WorkEvent.WORK_ACCEPTED, work, null)); // execute work final Worker worker = new Worker(work, workListener, startTimeout); executor.execute(worker); if (workType == WorkType.DO) { // wait for completion try { worker.waitForCompletion(); } catch (final InterruptedException e) { final WorkException workException = new WorkException("Work submission thread was interrupted", e); workException.setErrorCode(WorkException.INTERNAL); throw workException; } // if work threw an exception, rethrow it final WorkException workCompletedException = worker.getWorkException(); if (workCompletedException != null) { throw workCompletedException; } } else if (workType == WorkType.START) { // wait for work to start try { worker.waitForStart(); } catch (final InterruptedException e) { final WorkException workException = new WorkException("Work submission thread was interrupted", e); workException.setErrorCode(WorkException.INTERNAL); throw workException; } // if work threw a rejection exception, rethrow it (it is the exception for timeout) final WorkException workCompletedException = worker.getWorkException(); if (workCompletedException instanceof WorkRejectedException) { throw workCompletedException; } } return worker.getStartDelay(); } private class Worker implements Runnable { private final Work work; private final WorkListener workListener; private final long startTimeout; private final long created = System.currentTimeMillis(); private final CountDownLatch started = new CountDownLatch(1); private final CountDownLatch completed = new CountDownLatch(1); private long startDelay = UNKNOWN; private WorkException workException; public Worker(final Work work, final WorkListener workListener, final long startTimeout) { this.work = work; this.workListener = workListener; if (startTimeout <= 0) { this.startTimeout = INDEFINITE; } else { this.startTimeout = startTimeout; } } public void run() { try { // check if we have started within the specified limit startDelay = System.currentTimeMillis() - created; if (startDelay > startTimeout) { workException = new WorkRejectedException("Work not started within specified timeout " + startTimeout + "ms", WorkException.START_TIMED_OUT); workListener.workRejected(new WorkEvent(this, WorkEvent.WORK_REJECTED, work, workException, startTimeout)); return; } // notify listener that work has been started workListener.workStarted(new WorkEvent(SimpleWorkManager.this, WorkEvent.WORK_STARTED, work, null)); // officially started started.countDown(); // execute the real work workException = null; try { work.run(); } catch (final Throwable e) { workException = new WorkCompletedException(e); } finally { // notify listener that work completed (with an optional exception) workListener.workCompleted(new WorkEvent(SimpleWorkManager.this, WorkEvent.WORK_COMPLETED, work, workException)); } } finally { // assure that threads waiting for start are released started.countDown(); // Done completed.countDown(); } } public long getStartDelay() { return startDelay; } public WorkException getWorkException() { return workException; } public void waitForStart() throws InterruptedException { started.await(); } public void waitForCompletion() throws InterruptedException { completed.await(); } } private static final class LoggingWorkListener extends WorkAdapter { private final WorkType workType; private LoggingWorkListener(final WorkType workType) { this.workType = workType; } public void workRejected(final WorkEvent event) { // Don't log doWork or startWork since exception is propagated to caller if (workType == WorkType.DO || workType == WorkType.START) { return; } final WorkException exception = event.getException(); if (exception != null) { if (WorkException.START_TIMED_OUT.equals(exception.getErrorCode())) { logger.error(exception.getMessage()); } } } public void workCompleted(final WorkEvent event) { // Don't log doWork since exception is propagated to caller if (workType == WorkType.DO) { return; } Throwable cause = event.getException(); if (cause != null && cause.getCause() != null) { cause = cause.getCause(); } if (cause != null) { logger.error(event.getWork().toString(), cause); } } } }