package com.revolsys.gis.parallel; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.jexl.Expression; import org.apache.commons.jexl.JexlContext; import org.apache.commons.jexl.context.HashMapContext; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import com.revolsys.collection.map.ThreadSharedProperties; import com.revolsys.logging.Logs; import com.revolsys.parallel.ThreadInterruptedException; import com.revolsys.parallel.ThreadUtil; import com.revolsys.parallel.channel.Channel; import com.revolsys.parallel.channel.ClosedException; import com.revolsys.parallel.process.BaseInProcess; import com.revolsys.parallel.tools.ScriptExecutorRunnable; import com.revolsys.record.Record; import com.revolsys.util.JexlUtil; public class ScriptExecutorProcess extends BaseInProcess<Record> implements BeanFactoryAware { private final Map<String, Object> attributes = new HashMap<>(); private ExecutorService executor; private final Map<String, Expression> expressions = new HashMap<>(); private int maxConcurrentScripts = 1; private Map<String, String> parameters = new HashMap<>(); private String script; private final Set<Future<?>> tasks = new LinkedHashSet<>(); @Override protected void destroy() { try { while (!this.tasks.isEmpty()) { synchronized (this) { ThreadUtil.pause(this, 1000); for (final Iterator<Future<?>> taskIter = this.tasks.iterator(); taskIter.hasNext();) { final Future<?> task = taskIter.next(); if (task.isDone()) { taskIter.remove(); } } } } } finally { this.executor.shutdown(); } } private void executeScript(final Record record) { try { final JexlContext context = new HashMapContext(); final Map<String, Object> vars = new HashMap<>(this.attributes); vars.putAll(record); context.setVars(vars); final Map<String, Object> scriptParams = new HashMap<>(); scriptParams.putAll(this.attributes); for (final Entry<String, Expression> param : this.expressions.entrySet()) { final String key = param.getKey(); final Expression expression = param.getValue(); final Object value = JexlUtil.evaluateExpression(context, expression); scriptParams.put(key, value); } final ScriptExecutorRunnable scriptRunner = new ScriptExecutorRunnable(this.script, scriptParams); if (this.executor == null) { scriptRunner.run(); } else { while (this.tasks.size() >= this.maxConcurrentScripts) { try { synchronized (this) { ThreadUtil.pause(1000); for (final Iterator<Future<?>> taskIter = this.tasks.iterator(); taskIter .hasNext();) { final Future<?> task = taskIter.next(); if (task.isDone()) { taskIter.remove(); } } } } catch (final ThreadInterruptedException e) { throw new ClosedException(e); } } final Future<?> future = this.executor.submit(scriptRunner); this.tasks.add(future); } } catch (final ThreadDeath e) { throw e; } catch (final Throwable t) { Logs.error(this, t); } } public ExecutorService getExecutor() { return this.executor; } public int getMaxConcurrentScripts() { return this.maxConcurrentScripts; } public Map<String, String> getParameters() { return this.parameters; } public String getScript() { return this.script; } @Override protected void postRun(final Channel<Record> in) { this.tasks.clear(); if (this.executor != null) { this.executor.shutdownNow(); } } @Override protected void process(final Channel<Record> in, final Record object) { executeScript(object); } @Override public void setBeanFactory(final BeanFactory beanFactory) throws BeansException { this.attributes.putAll(ThreadSharedProperties.getProperties()); } public void setExecutor(final ExecutorService executor) { this.executor = executor; } public void setMaxConcurrentScripts(final int maxConcurrentScripts) { this.maxConcurrentScripts = maxConcurrentScripts; if (this.executor == null) { this.executor = new ThreadPoolExecutor(Math.min(maxConcurrentScripts, 10), maxConcurrentScripts, 10, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } } public void setParameters(final Map<String, String> parameters) { this.parameters = parameters; for (final Entry<String, String> param : parameters.entrySet()) { final String key = param.getKey(); final String value = param.getValue(); try { final Expression expression = JexlUtil.newExpression(value, "#\\{([^\\}]+)\\}"); this.expressions.put(key, expression); } catch (final Exception e) { throw new IllegalArgumentException("Expression not valid " + key + "=" + value); } } } public void setScript(final String script) { this.script = script; } }