/* * Copyright (c) 2016 Fraunhofer IGD * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * Fraunhofer IGD <http://www.igd.fraunhofer.de/> */ package de.fhg.igd.mapviewer.concurrency; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import de.fhg.igd.osgi.util.SingleServiceTracker; import de.fhg.igd.slf4jplus.ALogger; import de.fhg.igd.slf4jplus.ALoggerFactory; import de.fhg.igd.slf4jplus.ATransaction; /** * Helper for running concurrent jobs based on an {@link Executor} available as * OSGi service. * * @author Simon Templer */ public class Concurrency extends SingleServiceTracker<Executor>implements Executor { private static final ALogger log = ALoggerFactory.getLogger(Concurrency.class); /** * Job wrapper. Begins and ends log transactions if necessary * * @param <T> the job result type */ private static class JobWrapper<T> implements IJob<T> { private final IJob<T> job; private CallbackWrapper<T> callbackWrapper; /** * @param job the internal job */ public JobWrapper(IJob<T> job) { super(); this.job = job; } /** * Set a call-back wrapper * * @param callbackWrapper the call-back wrapper to set */ public void setCallbackWrapper(CallbackWrapper<T> callbackWrapper) { this.callbackWrapper = callbackWrapper; if (callbackWrapper != null) { callbackWrapper.setCallback(job.getCallback()); } } /** * @see IJob#getCallback() */ @Override public Callback<T> getCallback() { if (callbackWrapper != null) { return callbackWrapper; } else { return job.getCallback(); } } /** * @see IJob#getName() */ @Override public String getName() { return job.getName(); } /** * @see IJob#isBackground() */ @Override public boolean isBackground() { return job.isBackground(); } /** * @see IJob#isHidden() */ @Override public boolean isHidden() { return job.isHidden(); } /** * @see IJob#isLogTransaction() */ @Override public boolean isLogTransaction() { return job.isLogTransaction(); } /** * @see IJob#isModal() */ @Override public boolean isModal() { return job.isModal(); } /** * @see IJob#isExclusive() */ @Override public boolean isExclusive() { return job.isExclusive(); } /** * @see IJob#work(Progress) */ @Override public T work(Progress progress) throws Exception { ATransaction logTrans; if (job.isLogTransaction()) { logTrans = log.begin(getName()); } else { logTrans = null; } try { return job.work(progress); } finally { if (logTrans != null) { logTrans.end(); } } } } /** * Wrapper for call-backs that provides the means of getting notified on job * completion * * @param <T> the job result type */ private static abstract class CallbackWrapper<T> implements Callback<T> { /** * The inner call-back */ private Callback<T> callback; /** * @param callback the call-back to set */ public void setCallback(Callback<T> callback) { this.callback = callback; } /** * @see Callback#done(java.lang.Object) */ @Override public void done(T result) { try { if (callback != null) { callback.done(result); } } finally { finished(true, result, null); } } /** * Called when the job has been execution has been finished * * @param success if the job completed successfully * @param result the job result if any * @param error the error while executing the job if it was not * successful */ protected abstract void finished(boolean success, T result, Throwable error); /** * @see Callback#failed(Throwable) */ @Override public void failed(Throwable e) { try { if (callback != null) { callback.failed(e); } } finally { finished(false, null, e); } } } private static final Concurrency instance = new Concurrency(); /** * Get the job executor * * @return the job executor */ public static Executor getExecutor() { return instance; } /** * Get the concurrency instance * * @return the concurrency instance */ public static Concurrency getInstance() { return instance; } /** * Start/schedule a job if possible * * @param <T> the job result type * @param job the job * @throws NullPointerException if there is no {@link Executor} service * available */ public static <T> void startJob(IJob<T> job) { instance.start(new JobWrapper<T>(job)); } /** * Execute a job and wait for the result * * @param <T> the job result type * @param job the job * @return the job result * @throws ExecutionException if executing the job fails * @throws NullPointerException if there is no {@link Executor} service * available */ public static <T> T startAndWait(IJob<T> job) throws ExecutionException { final AtomicBoolean finished = new AtomicBoolean(false); final AtomicBoolean aSuccess = new AtomicBoolean(); final AtomicReference<T> aResult = new AtomicReference<T>(); final AtomicReference<Throwable> aError = new AtomicReference<Throwable>(); final Thread current = Thread.currentThread(); JobWrapper<T> exec = new JobWrapper<T>(job); exec.setCallbackWrapper(new CallbackWrapper<T>() { @Override protected void finished(boolean success, T result, Throwable error) { // set as finished finished.set(true); // set results aSuccess.set(success); aResult.set(result); aError.set(error); // notify thread synchronized (current) { current.notify(); } } }); instance.start(exec); synchronized (current) { while (!finished.get()) { try { current.wait(); } catch (InterruptedException e) { // ignore } } } if (aSuccess.get()) { return aResult.get(); } else { throw new ExecutionException("Error executing job: " + job.getName(), aError.get()); } } /** * Creates a {@link Concurrency} instance */ protected Concurrency() { super(Executor.class); } /** * @see Executor#start(IJob) */ @Override public <T> void start(IJob<T> job) { Executor exec = getService(); if (exec != null) { exec.start(job); } // TODO queue jobs for later execution if there is no service? else throw new NullPointerException("No executor service available"); } }