Java tutorial
/* * Copyright (C) 2006-2016 Talend Inc. - www.talend.com * * This source code is available under agreement available at * %InstallDIR%\features\org.talend.rcp.branding.%PRODUCTNAME%\%PRODUCTNAME%license.txt * * You should have received a copy of the agreement along with this program; if not, write to Talend SA 9 rue Pages * 92150 Suresnes, France */ package com.amalto.core.jobox; import java.io.File; import java.net.URL; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import com.amalto.core.jobox.component.JobAware; import com.amalto.core.jobox.component.JobDeploy; import com.amalto.core.jobox.component.JobInvoke; import com.amalto.core.jobox.component.JobInvoker; import com.amalto.core.jobox.component.MDMJobInvoker; import com.amalto.core.jobox.properties.StandardPropertiesStrategyFactory; import com.amalto.core.jobox.util.JobClassLoader; import com.amalto.core.jobox.util.JobNotFoundException; import com.amalto.core.jobox.util.JoboxConfig; import com.amalto.core.jobox.util.JoboxException; import com.amalto.core.jobox.util.JoboxUtil; import com.amalto.core.jobox.watch.DirMonitor; import com.amalto.core.jobox.watch.JoboxListener; public class JobContainer { private static final int WATCH_INTERVAL = 2000; /** * unique instance */ private static final JobContainer instance = new JobContainer(); private static final Logger LOGGER = Logger.getLogger(JobContainer.class); /** * Count how many job executor thread have requested for execution lock. */ private final AtomicInteger executionCount = new AtomicInteger(0); /** * * * Indicates whether a thread currently has exclusive access to the container (blocks all requests for execution till * the thread releases the exclusive lock). */ private final AtomicBoolean modificationLock = new AtomicBoolean(false); private final Map<JobInfo, JobClassLoader> jobLoadersPool = Collections .synchronizedMap(new HashMap<JobInfo, JobClassLoader>()); private JoboxConfig joboxConfig = null; private JobAware jobAware = null; private JobDeploy jobDeploy = null; private DirMonitor monitor; private Properties standardProperties = new Properties(); /** * Private constructor */ private JobContainer() { } /** * @return Returns the unique instance of this class. In order to improve the performance, removed synchronized, using pseudo * singleton mode */ public static JobContainer getUniqueInstance() { return instance; } /** * Initializes the Jobox container with the <code>props</code> properties. * * @param props A {@link Properties} instance used to create a {@link JoboxConfig}. */ public void init(Properties props) { // init config joboxConfig = new JoboxConfig(props); // check home File joboxDeployPath = new File(joboxConfig.getDeployPath()); File joboxWorkPath = new File(joboxConfig.getWorkPath()); if (!joboxDeployPath.exists()) { if (!joboxDeployPath.mkdirs()) { LOGGER.error("Create folder failed for '" + joboxDeployPath.getAbsolutePath() + "'."); } } if (!joboxWorkPath.exists()) { if (!joboxWorkPath.mkdirs()) { LOGGER.error("Create folder failed for '" + joboxWorkPath.getAbsolutePath() + "'."); } } LOGGER.info("Jobox Home is: " + joboxConfig.getJoboxHome()); //$NON-NLS-1$ // init component jobAware = new JobAware(joboxConfig); jobDeploy = new JobDeploy(joboxConfig); // clear work folder JoboxUtil.cleanFolder(joboxConfig.getWorkPath()); // redeploy all to work folder jobDeploy.deployAll(); // init classpath this.jobLoadersPool.clear(); List<JobInfo> currentJobs = jobAware.findJobsInBox(); for (JobInfo jobInfo : currentJobs) { URL[] urls = JoboxUtil.getClasspathURLs(jobInfo.getClasspath(), jobInfo); JobClassLoader cl = new JobClassLoader(urls); if (!this.jobLoadersPool.containsKey(jobInfo)) { this.jobLoadersPool.put(jobInfo, cl); } } // start monitor // Create the monitor monitor = new DirMonitor(WATCH_INTERVAL); // Add some files to listen for monitor.addFile(new File(this.getDeployDir())); // Add a jobox listener monitor.addListener(new JoboxListener()); // Initialize default system properties standardProperties = StandardPropertiesStrategyFactory.create().getStandardProperties(); } /** * @return Returns default JVM properties */ public Properties getStandardProperties() { return standardProperties; } public void updateJobLoadersPool(JobInfo jobInfo) { if (jobInfo == null) { throw new IllegalArgumentException("Job info argument can not be null."); //$NON-NLS-1$ } if (jobLoadersPool.containsKey(jobInfo)) { JobClassLoader jobClassLoader = jobLoadersPool.get(jobInfo); log("Removing " + jobClassLoader); //$NON-NLS-1$ jobLoadersPool.remove(jobInfo); } else { log("No previous class loader for " + jobInfo.getName());//$NON-NLS-1$ } URL[] urls = JoboxUtil.getClasspathURLs(jobInfo.getClasspath(), jobInfo); JobClassLoader cl = new JobClassLoader(urls); jobLoadersPool.put(jobInfo, cl); LOGGER.info("Adding new class loader " + cl); //$NON-NLS-1$ } public void removeFromJobLoadersPool(String jobEntityName) { // parse name and version String jobVersion = StringUtils.EMPTY; //$NON-NLS-1$ String jobName = StringUtils.EMPTY; //$NON-NLS-1$ Matcher m = JobAware.jobVersionNamePattern.matcher(jobEntityName); while (m.find()) { jobName = m.group(1); jobVersion = m.group(m.groupCount()); } JobInfo jobInfo = new JobInfo(jobName, jobVersion); if (this.jobLoadersPool.containsKey(jobInfo)) { JobClassLoader jobClassLoader = jobLoadersPool.get(jobInfo); LOGGER.info("Removing class loader " + jobClassLoader); //$NON-NLS-1$ jobLoadersPool.remove(jobInfo); } } public JobAware getJobAware() { return jobAware; } public JobDeploy getJobDeployer() { return jobDeploy; } /** * Return the {@link JobInvoker} implementation to execute the job. * * @param jobName A job name * @param version A job version * @return A {@link JobInvoker} implementation depending on job. * @throws JobNotFoundException if {@link #getJobInfo(String, String)} returns null (i.e. the job does not exist). */ public JobInvoker getJobInvoker(String jobName, String version) { JobInfo jobInfo = getJobInfo(jobName, version); if (jobInfo == null) { throw new JobNotFoundException(jobName, version); } Class jobClass = getJobClass(jobInfo); Class[] interfaces = jobClass.getInterfaces(); if (interfaces.length > 0) { for (Class currentInterface : interfaces) { if ("routines.system.api.TalendMDMJob".equals(currentInterface.getName())) { //$NON-NLS-1$ return new MDMJobInvoker(jobName, version); } } } // Default invocation return new JobInvoke(jobName, version); } /** * @return Returns the deploy directory for the jobs. * @see com.amalto.core.jobox.util.JoboxConfig#getDeployPath() */ public String getDeployDir() { return joboxConfig.getDeployPath(); } /** * @return Returns the deploy directory for the jobs. * @see com.amalto.core.jobox.util.JoboxConfig#getWorkPath() */ public String getWorkDir() { return joboxConfig.getWorkPath(); } /** * @param jobName A job name * @param jobVersion A job version * @return Returns the {@link JobInfo} instance for this job if it exists, <code>null</code> otherwise. */ public JobInfo getJobInfo(String jobName, String jobVersion) { JobInfo jobInfoPK = new JobInfo(jobName, jobVersion); Set<JobInfo> jobInformation = jobLoadersPool.keySet(); for (JobInfo jobInfo : jobInformation) { if (jobInfo.equals(jobInfoPK)) { return jobInfo; } } return null; } /** * @return Returns information about all jobs deployed in this Jobox container. */ public Set<JobInfo> getAllJobInfo() { return jobLoadersPool.keySet(); } /** * <p> * Loads the job main class using a specific class loader (isolated from caller class loader). * </p> * <p> * Please note that this method also changes {@link Thread#getContextClassLoader()} <b>during</b> load, if * caller's class loader isn't already the job class loader (as returned by {@link #getJobClassLoader(JobInfo)}). * </p> * <p> * Once this method is completed {@link Thread#getContextClassLoader()} is equals to the caller's class loader. * </p> * * @param jobInfo A {@link JobInfo} instance. * @return The entry point class for job execution. */ public Class getJobClass(JobInfo jobInfo) { ClassLoader previousCallLoader = Thread.currentThread().getContextClassLoader(); ClassLoader jobClassLoader = getJobClassLoader(jobInfo); try { if (previousCallLoader != jobClassLoader) { // TMDM-1733: Change context class loader during job class load. Thread.currentThread().setContextClassLoader(jobClassLoader); } return jobClassLoader.loadClass(jobInfo.getMainClass()); } catch (ClassNotFoundException e) { LOGGER.error("Could not find class '" + jobInfo.getMainClass() + "'", e); return null; } finally { if (previousCallLoader != jobClassLoader) { Thread.currentThread().setContextClassLoader(previousCallLoader); } } } public ClassLoader getJobClassLoader(JobInfo jobInfo) { return this.jobLoadersPool.get(jobInfo); } public void setContextStrToBeSaved(String entryPath, String cxt) { monitor.changeContextStr(entryPath, cxt); int idxSeparator = entryPath.lastIndexOf(File.separatorChar); if (idxSeparator != -1) { String entryName = entryPath.substring(idxSeparator + 1); // undeploy JobContainer.getUniqueInstance().getJobDeployer().undeploy(entryName); // remove classpath JobContainer.getUniqueInstance().removeFromJobLoadersPool(entryName); jobDeploy.deploy(entryName); // add to classpath JobInfo jobInfo = JobContainer.getUniqueInstance().getJobAware() .loadJobInfo(JoboxUtil.trimExtension(entryName)); JobContainer.getUniqueInstance().updateJobLoadersPool(jobInfo); } } /** * Locks repository <i>if needed</i>, this depends on <code>forModification</code> if: * <ul> * <li>true: calling thread is a deploy thread as it's trying to modify the container.</li> * <li>false: calling thread is an executor thread only trying to see if no 'deploy' thread is working on container</li> * </ul> * <p> * If calling thread is a deploy thread, it will wait for all executors to complete (whatever job they may be executing). * Once wait is over, the thread will acquire all execution permits so no executor thread can run before the deploy * thread is complete. * </p> * @param forModification <code>true</code> if calling thread is a deploy thread (a thread that performs modification * on the container), <code>false</code> otherwise. * @see #unlock(boolean) */ public void lock(boolean forModification) { try { if (forModification) { synchronized (executionCount) { while (executionCount.get() > 0) { log("[MOD] " + Thread.currentThread().getName() + " waiting for executors to finish"); //$NON-NLS-1$ //$NON-NLS-2$ executionCount.wait(); } } synchronized (modificationLock) { while (modificationLock.get()) { log("[MOD] " + Thread.currentThread().getName() //$NON-NLS-1$ + " waiting for exclusive modification lock"); //$NON-NLS-1$ modificationLock.wait(); } modificationLock.set(true); } log("[MOD] " + Thread.currentThread().getName() + " acquired exclusive modification lock"); //$NON-NLS-1$ //$NON-NLS-2$ } else { synchronized (modificationLock) { while (modificationLock.get()) { log("[EXE] " + Thread.currentThread().getName() //$NON-NLS-1$ + " waiting for exclusive modification lock release."); //$NON-NLS-1$ modificationLock.wait(); } } synchronized (executionCount) { executionCount.getAndIncrement(); } log("[EXE] " + Thread.currentThread().getName() + " acquired execution lock"); //$NON-NLS-1$ //$NON-NLS-2$ } } catch (InterruptedException e) { throw new JoboxException(e); } } /** * Unlocks repository, action actually performed depends on <code>forModification</code>, if: * <ul> * <li>true: calling thread is a deploy thread as it's trying to modify the container.</li> * <li>false: calling thread is an executor thread only trying to see if no 'deploy' thread is working on container</li> * </ul> * <p> * If calling thread is a deploy thread, it will release exclusive lock so all executors threads can now * run. * </p> * <p> * If calling thread is a executor thread, it will decrease the count of current executors threads and wake up any * thread waiting for executor count = 0. * </p> * * @param forModification <code>true</code> if calling thread is a deploy thread (a thread that performs modification * on the container), <code>false</code> otherwise. * @see #lock(boolean) */ public void unlock(boolean forModification) { if (forModification) { synchronized (modificationLock) { modificationLock.set(false); modificationLock.notifyAll(); } log("[MOD] " + Thread.currentThread().getName() + " released exclusive modification lock."); //$NON-NLS-1$ //$NON-NLS-2$ } else { synchronized (executionCount) { executionCount.getAndDecrement(); executionCount.notifyAll(); } log("[EXE] " + Thread.currentThread().getName() + " released executor lock"); //$NON-NLS-1$ //$NON-NLS-2$ } } private static synchronized void log(String msg) { if (LOGGER.isDebugEnabled()) { LOGGER.debug(msg); } } public void close() { jobLoadersPool.clear(); monitor.stop(); } }