/* ********************************************************************** /* * NOTE: This copyright does *not* cover user programs that use Hyperic * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004-2012], VMware, Inc. * This file is part of Hyperic. * * Hyperic is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program is distributed * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.tools.dbmigrate; import java.sql.Connection; import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.concurrent.BlockingDeque; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.hyperic.util.MultiRuntimeException; /** * * Map reduce service class which spawns configurable amount for worker threads to share the load of a task buffer<br/> * and awaits for all workers to finish prior to returning to the caller. * <p> * The class delegates the worker instance creation to the {@link WorkerFactory} formal argument instance. * </p> */ public class Forker { /** * Spawns configurable amount of worker threads and awaits until all workers are finished prior to returning to the caller * @param bufferSize - number of elements in the task buffer. Used to configure the number of workers as if the value is<br/> * smaller than the value of the maxWorkers, the number of workers spawned would be the bufferSize. * @param maxWorkers number of threads to initialize unless bigger than the buffer size. * @param context {@link ForkContext} instance containing the {@link WorkerFactory} and the workers' sink * @return a list of {@link Future} each containing an array of entities processed by the * @throws Throwable */ public static final <V, T extends Callable<V[]>> List<Future<V[]>> fork(final int bufferSize, final int maxWorkers, final ForkContext<V, T> context) throws Throwable { ExecutorService executorPool = null; try{ int iNoOfWorkers = bufferSize < maxWorkers ? bufferSize : maxWorkers; final List<Future<V[]>> workersResponses = new ArrayList<Future<V[]>>(iNoOfWorkers); executorPool = Executors.newFixedThreadPool(iNoOfWorkers); context.inverseSemaphore = new CountDownLatch(iNoOfWorkers); Future<V[]> workerResponse = null; Callable<V[]> worker = null; for (int i = 0; i < iNoOfWorkers; i++) { worker = context.workerFactory.newWorker(context); workerResponse = executorPool.submit(worker); workersResponses.add(workerResponse); }// EO while there are more workers context.inverseSemaphore.await(); return workersResponses; }catch (Throwable t) { Utils.printStackTrace(t); throw t; }finally { if (executorPool != null) executorPool.shutdown(); }//EO catch block }// EOM /** * Base worker class handling the task polling, error handler and connection closure * @author guy * * @param <V> */ public static abstract class ForkWorker<V> implements Callable<V[]> { protected final Connection conn; private final CountDownLatch countdownSemaphore; private Class<V> entityType ; protected BlockingDeque<V> sink; protected MultiRuntimeException accumulatedErrors ; protected ForkWorker(final CountDownLatch countdownSemaphore, Connection conn, BlockingDeque<V> sink, final Class<V> clsEntityType, final MultiRuntimeException accumulatedErrors) { this.countdownSemaphore = countdownSemaphore; this.conn = conn; this.sink = sink; this.entityType = clsEntityType ; this.accumulatedErrors = accumulatedErrors ; }//EOM protected abstract void callInner(final V entity) throws Throwable; @SuppressWarnings("unchecked") public V[] call() throws Exception { final List<V> processedEntities = new ArrayList<V>(); MultiRuntimeException thrown = null; try{ V entity = null; Throwable innerException = null; final String errorMsg = "[" + this.getClass().getName() + "] : an Error had occured while processing entity: " ; boolean hadPolledFirst = false ; while ((entity = (hadPolledFirst ? this.sink.pollLast() : this.sink.poll())) != null){ try { processedEntities.add(entity); callInner(entity); hadPolledFirst = !hadPolledFirst ; }catch (Throwable t2) { final String msg = errorMsg + entity ; this.reportError(t2, msg) ; Utils.printStackTrace(t2, msg); innerException = t2; }finally { try { if(innerException == null) { this.conn.commit(); }else { rollbackEntity(entity, innerException); innerException = null; this.conn.rollback(); }//EO else if there was an error }catch (Throwable t2) { Utils.printStackTrace(t2, errorMsg + entity); MultiRuntimeException.newMultiRuntimeException(thrown, t2); }//EO inner catch block }//EO catch block }//EO while there are more entities } catch (Throwable t1) { Utils.printStackTrace(t1); MultiRuntimeException.newMultiRuntimeException(thrown, t1); } finally { /*try { Utils.close(thrown != null ? Utils.ROLLBACK_INSTRUCTION_FLAG : Utils.NOOP_INSTRUCTION_FLAG, new Object[] { this.conn }); } catch (Throwable t1) { MultiRuntimeException.newMultiRuntimeException(thrown, t1); }//EO inner catch block */ try { this.dispose(thrown) ; } catch (Throwable t1) { MultiRuntimeException.newMultiRuntimeException(thrown, t1); }//EO inner catch block if (this.countdownSemaphore != null) this.countdownSemaphore.countDown(); if (thrown != null) throw thrown; }//EO catch block final V[] arrResponse = (V[]) java.lang.reflect.Array.newInstance(this.entityType, processedEntities.size()) ; return processedEntities.toArray(arrResponse); }//EOM protected synchronized void reportError(final Throwable throwable, final String errorMsg) { this.accumulatedErrors.addThrowable(throwable, errorMsg) ; }//EOM protected void dispose(final MultiRuntimeException thrown) throws Throwable { try { Utils.close(thrown != null ? Utils.ROLLBACK_INSTRUCTION_FLAG : Utils.NOOP_INSTRUCTION_FLAG, new Object[] { this.conn }); } catch (Throwable t1) { MultiRuntimeException.newMultiRuntimeException(thrown, t1); }//EO inner catch block }//EOM protected void rollbackEntity(final V entity, final Throwable t) { /*NOOP*/}//EOM }//EOM public static abstract interface WorkerFactory<V, T extends Callable<V[]>> { T newWorker(final Forker.ForkContext<V, T> paramForkContext) throws Throwable; }//EO interface WorkerFactory @SuppressWarnings({"rawtypes"}) public static class ForkContext<V, T extends Callable<V[]>> extends HashMap<Object,Object> { private static final long serialVersionUID = -1221616165268097942L; private BlockingDeque<V> sink; private Forker.WorkerFactory<V, T> workerFactory; private CountDownLatch inverseSemaphore; private MultiRuntimeException accumulatedErrors ; private Hashtable env; public ForkContext(BlockingDeque<V> sink, Forker.WorkerFactory<V, T> workerFactory, Hashtable env) { this.sink = sink; this.workerFactory = workerFactory; this.env = env; this.accumulatedErrors = new MultiRuntimeException() ; }//EOM public final Hashtable getEnv() { return this.env; }//EOM public final CountDownLatch getSemaphore() { return this.inverseSemaphore; }//EOM public final BlockingDeque<V> getSink() { return this.sink; }//EOM public final MultiRuntimeException getAccumulatedErrorsSink() { return this.accumulatedErrors ; }//EOM }//EO inner class ForkContext }//EOC