/* * Copyright 2006-2012 The Scriptella Project Team. * * 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 scriptella.execution; import scriptella.configuration.ConfigurationEl; import scriptella.configuration.ConfigurationFactory; import scriptella.core.Session; import scriptella.core.SystemException; import scriptella.core.ThreadSafe; import scriptella.interactive.ProgressCallback; import scriptella.interactive.ProgressIndicator; import scriptella.util.CollectionUtils; import scriptella.util.IOUtils; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.Map; import java.util.concurrent.Callable; import java.util.logging.Level; import java.util.logging.Logger; /** * Executor for ETL files. * <p>Use {@link ConfigurationFactory} to parse script files and to configure * the executor. * <p>The usage scenario of this class may be described using the following steps: * <ul> * <li>{@link #EtlExecutor(scriptella.configuration.ConfigurationEl)} Create an instance of this class * and pass a {@link scriptella.configuration.ConfigurationFactory#createConfiguration() script file configuration}. * <li>{@link #execute() Execute} the script * </ul> * </pre></code> * <p>Additionally simplified helper methods are declared in this class: * <ul> * <li>{@link #newExecutor(java.io.File)} * <li>{@link #newExecutor(java.net.URL)} * <li>{@link #newExecutor(java.net.URL, java.util.Map)} * </ul> * <p/> * <h3>ETL Cancellation</h3> * Scriptella execution model relies on a standard Java {@link Thread#interrupt()} mechanism. * <p>To interrupt the ETL execution invoke {@link Thread#interrupt()} on a thread * which {@link #execute() started} ETL operation. As a part of interruption process * the engine tries to roll back all changes made during the ETL operation. * <p>{@link java.util.concurrent.ExecutorService} and {@link java.util.concurrent.Future} * can also be used to control ETL execution. * <h3>Integration with third-party systems</h3> * For convenience EtlExecutor implements {@link Runnable} and {@link java.util.concurrent.Callable}. * This feature simplifies integration of Scriptella executors with {@link java.util.concurrent.Executors} * or other systems like Spring/Quartz etc. It also minimizes application code dependency on Scriptella. * * @author Fyodor Kupolov * @version 1.0 */ public class EtlExecutor implements Runnable, Callable<ExecutionStatistics> { private static final Logger LOG = Logger.getLogger(EtlExecutor.class.getName()); private ConfigurationEl configuration; private boolean jmxEnabled; private boolean suppressStatistics; /** * Creates ETL executor. */ public EtlExecutor() { } /** * Creates an ETL executor for specified configuration file. * * @param configuration ETL configuration. */ public EtlExecutor(ConfigurationEl configuration) { this.configuration = configuration; } /** * Returns ETL configuration for this executor. * * @return ETL configuration. */ public ConfigurationEl getConfiguration() { return configuration; } /** * Sets ETL configuration. * * @param configuration ETL configuration. */ public void setConfiguration(final ConfigurationEl configuration) { this.configuration = configuration; } /** * Returns true if monitoring/management via JMX is enabled. * <p>If jmxEnabled=true the executor registers MBeans for executed ETL files. * The object names of the mbeans have the following form: * <code>scriptella: type=etl,url="ETL_FILE_URL"</code> * * @return true if monitoring/management via JMX is enabled. */ public boolean isJmxEnabled() { return jmxEnabled; } /** * Enables or disables ETL monitoring/management via JMX. * <p>If jmxEnabled=true the executor registers MBeans for executed ETL files. * The object names of the mbeans have the following form: * <code>scriptella: type=etl,url="ETL_FILE_URL"</code> * * @param jmxEnabled true if monitoring/management via JMX is enabled. * @see scriptella.execution.JmxEtlManagerMBean */ public void setJmxEnabled(boolean jmxEnabled) { this.jmxEnabled = jmxEnabled; } /** * Getter for {@link #setSuppressStatistics(boolean) suppressStatistics} property. * @return true if statistics collection is disabled. Default value is false. */ public boolean isSuppressStatistics() { return suppressStatistics; } /** * Enables or disables collecting of statistics. Default value is false, which means statistics is collected. * <p>Setting this option to <code>true</code> may improve performance in some cases. * @param suppressStatistics true if statistics collection should be disabled. */ public void setSuppressStatistics(boolean suppressStatistics) { this.suppressStatistics = suppressStatistics; } /** * Executes ETL based on a specified configuration. * * @return execution statistics for ETL execution. * @throws EtlExecutorException if ETL fails. * @see #execute(scriptella.interactive.ProgressIndicator) */ @ThreadSafe public ExecutionStatistics execute() throws EtlExecutorException { return execute((ProgressIndicator) null); } /** * Executes ETL based on a specified configuration. * * @param indicator progress indicator to use. * @return execution statistics for ETL execution. * @throws EtlExecutorException if ETL fails. */ @ThreadSafe public ExecutionStatistics execute(final ProgressIndicator indicator) throws EtlExecutorException { EtlContext ctx = null; JmxEtlManager etlManager = null; try { ctx = prepare(indicator); if (jmxEnabled) { etlManager = new JmxEtlManager(ctx); etlManager.register(); } execute(ctx); ctx.getProgressCallback().step(5, "Commiting transactions"); commitAll(ctx); } catch (Throwable e) { if (ctx != null) { rollbackAll(ctx); } throw new EtlExecutorException(e); } finally { if (ctx != null) { closeAll(ctx); ctx.getStatisticsBuilder().etlComplete(); ctx.getProgressCallback().complete(); } if (etlManager != null) { etlManager.unregister(); } } return ctx.getStatisticsBuilder().getStatistics(); } void rollbackAll(final EtlContext ctx) { try { ctx.session.rollback(); } catch (Exception e) { LOG.log(Level.SEVERE, "Unable to rollback script", e); } } void commitAll(final EtlContext ctx) { ctx.session.commit(); } void closeAll(final EtlContext ctx) { ctx.session.close(); } private void execute(final EtlContext ctx) { final ProgressCallback oldProgress = ctx.getProgressCallback(); final ProgressCallback p = oldProgress.fork(85, 100); final ProgressCallback p2 = p.fork(100); ctx.setProgressCallback(p2); ctx.session.execute(ctx); p.complete(); ctx.setProgressCallback(oldProgress); } /** * Prepares the scripts context. * * @param indicator progress indicator to use. * @return prepared scripts context. */ protected EtlContext prepare(final ProgressIndicator indicator) { EtlContext ctx = new EtlContext(!suppressStatistics); ctx.getStatisticsBuilder().etlStarted(); ctx.setBaseURL(configuration.getDocumentUrl()); ctx.setProgressCallback(new ProgressCallback(100, indicator)); final ProgressCallback progress = ctx.getProgressCallback(); progress.step(1, "Initializing properties"); ctx.setProperties(configuration.getParameters()); ctx.setProgressCallback(progress.fork(9, 100)); ctx.session = new Session(configuration, ctx); ctx.getProgressCallback().complete(); ctx.setProgressCallback(progress); //Restoring return ctx; } /** * Converts file to URL and invokes {@link #newExecutor(java.net.URL)}. * * @param scriptFile ETL file. * @return configured instance of script executor. * @see #newExecutor(java.net.URL) */ public static EtlExecutor newExecutor(final File scriptFile) { try { return newExecutor(IOUtils.toUrl(scriptFile)); } catch (MalformedURLException e) { throw new IllegalArgumentException(e.getMessage(), e); } } /** * Helper method to create a new ScriptExecutor for specified script URL. * <p>Calls {@link #newExecutor(java.net.URL, java.util.Map)} and passes {@link System#getProperties() System properties} * as external properties. * * @param scriptFileUrl URL of script file. * @return configured instance of script executor. */ @ThreadSafe public static EtlExecutor newExecutor(final URL scriptFileUrl) { return newExecutor(scriptFileUrl, CollectionUtils.asMap(System.getProperties())); } /** * Helper method to create a new ScriptExecutor for specified script URL. * * @param scriptFileUrl URL of script file. * @param externalProperties see {@link ConfigurationFactory#setExternalParameters(java.util.Map)} * @return configured instance of script executor. * @see ConfigurationFactory */ @ThreadSafe public static EtlExecutor newExecutor(final URL scriptFileUrl, final Map<String, ?> externalProperties) { ConfigurationFactory cf = new ConfigurationFactory(); cf.setResourceURL(scriptFileUrl); if (externalProperties != null) { cf.setExternalParameters(externalProperties); } return new EtlExecutor(cf.createConfiguration()); } //Runnable/Callable convenience interfaces /** * A runnable adapter for {@link #execute()} method. * <p>Please note that due to a checked * exceptions limitation a {@link scriptella.core.SystemException} is thrown instead of * the {@link scriptella.execution.EtlExecutorException}. * * @throws SystemException a wrapped {@link scriptella.execution.EtlExecutorException}. * @see #execute() */ public void run() throws SystemException { try { execute(); } catch (EtlExecutorException e) { throw new SystemException(e.getMessage(), e); } } /** * A synonym for {@link #execute()}. */ public ExecutionStatistics call() throws EtlExecutorException { return execute(); } }