/* (c) 2014 - 2015 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wps.executor; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.geoserver.platform.ExtensionPriority; import org.geoserver.threadlocals.ThreadLocalsTransfer; import org.geoserver.wps.WPSException; import org.geoserver.wps.process.GeoServerProcessors; import org.geoserver.wps.resource.WPSResourceManager; import org.geotools.process.Process; import org.geotools.process.ProcessException; import org.geotools.process.ProcessFactory; import org.opengis.feature.type.Name; import org.opengis.util.ProgressListener; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextRefreshedEvent; public class DefaultProcessManager implements ProcessManager, ExtensionPriority, ApplicationListener<ApplicationEvent> { ConcurrentHashMap<String, Future<Map<String, Object>>> executions = new ConcurrentHashMap<String, Future<Map<String, Object>>>(); ThreadPoolExecutor synchService; ThreadPoolExecutor asynchService; WPSResourceManager resourceManager; public DefaultProcessManager(WPSResourceManager resourceManager) { this.resourceManager = resourceManager; } public void setMaxAsynchronousProcesses(int maxAsynchronousProcesses) { if(asynchService == null) { // create a fixed size pool. If we allow a delta between core and max // the pool will create new threads only if the queue is full, but the linked queue never is asynchService = new ThreadPoolExecutor(maxAsynchronousProcesses, maxAsynchronousProcesses, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } else { asynchService.setCorePoolSize(maxAsynchronousProcesses); asynchService.setMaximumPoolSize(maxAsynchronousProcesses); } } public void setMaxSynchronousProcesses(int maxSynchronousProcesses) { if(synchService == null) { // create a fixed size pool. If we allow a delta between core and max // the pool will create new threads only if the queue is full, but the linked queue never is synchService = new ThreadPoolExecutor(maxSynchronousProcesses, maxSynchronousProcesses, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } else { synchService.setCorePoolSize(maxSynchronousProcesses); synchService.setMaximumPoolSize(maxSynchronousProcesses); } } @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextRefreshedEvent) { if (event instanceof ContextClosedEvent) { synchService.shutdownNow(); asynchService.shutdownNow(); } } } /** * We can handle everything, other process managers will have to use a higher priority than us */ @Override public boolean canHandle(Name processName) { return true; } @Override public Map<String, Object> submitChained(String executionId, Name processName, Map<String, Object> inputs, ProgressListener listener) throws ProcessException { // straight execution, no thread pooling, we're already running in the parent process thread ProcessFactory pf = GeoServerProcessors.createProcessFactory(processName, true); if (pf == null) { throw new WPSException("No such process: " + processName); } // execute the process in the same thread as the caller Process p = pf.create(processName); Map<String, Object> result = p.execute(inputs, listener); return result; } @Override public void submit(String executionId, Name processName, Map<String, Object> inputs, ProgressListener listener, boolean background) throws ProcessException { ProcessCallable callable = new ProcessCallable(processName, inputs, listener); Future<Map<String, Object>> future; if(background) { future = asynchService.submit(callable); } else { future = synchService.submit(callable); } executions.put(executionId, future); } @Override public Map<String, Object> getOutput(String executionId, long timeout) throws ProcessException { Future<Map<String, Object>> future = executions.get(executionId); if (future == null) { return null; } boolean timedOut = false; try { Map<String, Object> result; if (timeout <= 0) { result = future.get(); } else { result = future.get(timeout, TimeUnit.MILLISECONDS); } return result; } catch (TimeoutException e) { timedOut = true; throw new ProcessException(e); } catch (Exception e) { if(e instanceof ExecutionException && e.getCause() instanceof Exception) { e = (Exception) e.getCause(); } if (e instanceof ProcessException) { throw (ProcessException) e; } else if (e instanceof WPSException) { throw (WPSException) e; } else { throw new ProcessException("Process execution " + executionId + " failed", e); } } finally { if (!timedOut) { // we're done executions.remove(executionId); } } } @Override public void cancel(String executionId) { Future future = executions.get(executionId); if (future != null) { future.cancel(true); } } @Override public int getPriority() { return ExtensionPriority.LOWEST; } class ProcessCallable implements Callable<Map<String, Object>> { Name processName; Map<String, Object> inputs; ThreadLocalsTransfer threadLocalTransfer; ProgressListener listener; public ProcessCallable(Name processName, Map<String, Object> inputs, ProgressListener listener) { this.processName = processName; this.inputs = inputs; this.listener = listener; this.threadLocalTransfer = new ThreadLocalsTransfer(); } @Override public Map<String, Object> call() throws Exception { try { // transfer the thread locals to this execution context threadLocalTransfer.apply(); ProcessFactory pf = GeoServerProcessors.createProcessFactory(processName, true); if (pf == null) { throw new WPSException("No such process: " + processName); } // execute the process Map<String, Object> result = null; Process p = pf.create(processName); result = p.execute(inputs, listener); return result; } finally { // clean up the thread locals threadLocalTransfer.cleanup(); } } } }