All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sangupta.jerry.batch.BatchJob Maven / Gradle / Ivy

/**
 *
 * jerry - Common Java Functionality
 * Copyright (c) 2012-2016, Sandeep Gupta
 *
 * http://sangupta.com/projects/jerry-core
 *
 * Licensed 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 com.sangupta.jerry.batch;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableCollection;
import com.google.common.util.concurrent.Service;
import com.google.common.util.concurrent.ServiceManager;
import com.sangupta.jerry.util.AssertUtils;

/**
 * This is the main class that builds, instantiates and controls one particular
 * batch job. This way multiple batch jobs can be executed in parallel.
 * 
 * @author sangupta
 *
 * @since 3.0.0
 */
public abstract class BatchJob {
	
	/**
	 * My private logger
	 */
	private static final Logger LOGGER = LoggerFactory.getLogger(BatchJob.class);
	
	/**
	 * The job name
	 */
	protected final String jobName;
	
	/**
	 * The internal {@link ServiceManager} that manages execution of all threads
	 * 
	 */
	private ServiceManager serviceManager;
	
	/**
	 * Return the job items over which we should work. Can return
	 * null to signal there are no current job items.
	 * 
	 * @return the job item
	 */
	protected abstract T getJobItem();
	
	/**
	 * Time in milliseconds to wait during shutdown, before each service worker
	 * thread will be killed to stop all services
	 * 
	 * @return the time in milliseconds
	 */
	protected abstract long getShutdownWaitTimeMillis();
	
	/**
	 * Get the {@link BatchJobItemExecutor} - the executor that process one
	 * given job item.
	 * 
	 * @return the associated {@link BatchJobItemExecutor}
	 */
	protected abstract BatchJobItemExecutor getJobPieceExecutor();
	
	/**
	 * Extension point to do something before worker threads are initialized.
	 * Returning a true will continue further in the setup.
	 * Returning a false will immediately halt creation of workers.
	 * 
	 * @return true to proceed ahead, false to stop
	 *         initialization
	 */
	protected boolean beforeWorkersInitialize() {
		return true;
	}
	
	/**
	 * Extension point to do something after worker threads have been
	 * initialized. Returning a true will continue further in the
	 * setup. Returning a false will immediately halt working
	 * further.
	 * 
	 * @return true if workers should start processing,
	 *         false to stop immediately
	 */
	protected boolean afterWorkersInitialize() {
		return true;
	}
	
	/**
	 * Constructor
	 * 
	 * @param jobName
	 *            the name of the job to use
	 * 
	 * @throws IllegalArgumentException
	 *             if jobName is null/empty
	 */
	public BatchJob(String jobName) {
		if(AssertUtils.isEmpty(jobName)) {
			throw new IllegalArgumentException("Job name cannot be null/empty");
		}
		
		// set the crawler name
		this.jobName = jobName;
		
		// this should be the last line in the crawler
		BatchCentral.addJob(this);
	}
	
	/**
	 * Start this batch job asynchronously.
	 * 
	 * @param numThreads
	 *            the number of threads to use for processing the job
	 * 
	 * @return true if batch job was started, false
	 *         otherwise
	 * 
	 * @throws IllegalArgumentException
	 *             if the number of threads is less than or equal to zero
	 */
	public boolean startJobAsync(int numThreads) {
		if(numThreads <= 0) {
			throw new IllegalArgumentException("Number of threads cannot be less than or equal to zero");
		}
		
		// running pre-initialization for job
		boolean moveAhead = beforeWorkersInitialize();
		if(!moveAhead) {
			LOGGER.info("Pre-initialization of job does not want us to move forward.");
			return false;
		}
		
		// start job
		LOGGER.info("Creating {} number of job worker threads for job: {}", numThreads, this.jobName);
		
		// create a new guava service manager to manage threads
		List services = new ArrayList<>();
		
		final Callable itemReader = new Callable() {
			
			@Override
			public T call() throws Exception {
				return BatchJob.this.getJobItem();
			}
			
		};
		
		for(int index = 0; index < numThreads; index++) {
			// create a new worker
			BatchWorker worker = new BatchWorker(this.jobName, "Job-Worker-" + this.jobName + "-" + index, itemReader, this.getJobPieceExecutor());
			
			// add to list of workers
			services.add(worker);
		}
		
		// create manager
		this.serviceManager = new ServiceManager(services);

		// and start the server
		this.serviceManager.startAsync();
		
		LOGGER.info("All worker threads have now started.Running after workers initialize...");
		
		// do post initialization
		afterWorkersInitialize();
		
		// return true
		return true;
	}
	
	/**
	 * Signal all worker threads to stop.
	 * 
	 */
	public void stopAsync() {
		this.serviceManager.stopAsync();
	}
	
	/**
	 * Shutdown this crawler instance, invoking shutdown on all
	 * worker threads.
	 * 
	 */
	public void shutdown() {
		// remove this instance from central
		BatchCentral.removeJob(this);
		
		// check for service manager
		if(this.serviceManager == null) {
			return;
		}
		
		LOGGER.debug("Stopping all services...");
		this.serviceManager.stopAsync();
		
		LOGGER.debug("Changing state of each worker manually...");
		ImmutableCollection services = this.serviceManager.servicesByState().values();
		for(Service service : services) {
			if(service == null) {
				continue;
			}
			
			@SuppressWarnings("unchecked")
			BatchWorker worker = (BatchWorker) service;
			
			// trigger shutdown on all workers
			worker.triggerShutdown();
		}
		
		// wait for stipulated time to shutdown
		try {
			LOGGER.debug("Waiting for all thread to die out...");
			this.serviceManager.awaitStopped(this.getShutdownWaitTimeMillis(), TimeUnit.MILLISECONDS);
		} catch (TimeoutException e) {
			LOGGER.error("Unable to wait for stopping all threads", e);
		}
	}
	
	/**
	 * Wait for all service threads to complete - either terminate naturally
	 * or fail.
	 * 
	 */
	public void waitForCompletion() {
		if(this.serviceManager == null) {
			return;
		}
		
		this.serviceManager.awaitStopped();
	}
	
	/**
	 * Return the name of this crawler.
	 * 
	 * @return the name of the job
	 */
	public String getJobName() {
		return this.jobName;
	}
	
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy