org.geoserver.wps.executor.DefaultProcessManager.java Source code

Java tutorial

Introduction

Here is the source code for org.geoserver.wps.executor.DefaultProcessManager.java

Source

/* Copyright (c) 2001 - 2013 OpenPlans - www.openplans.org. All rights reserved.
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package org.geoserver.wps.executor;

import java.util.ArrayList;
import java.util.List;
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.logging.Level;
import java.util.logging.Logger;

import org.geoserver.platform.ExtensionPriority;
import org.geoserver.threadlocals.ThreadLocalsTransfer;
import org.geoserver.wps.WPSException;
import org.geoserver.wps.executor.ExecutionStatus.ProcessState;
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.geotools.util.logging.Logging;
import org.opengis.feature.type.Name;
import org.opengis.util.InternationalString;
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, ExecutionStatusEx> executions = new ConcurrentHashMap<String, DefaultProcessManager.ExecutionStatusEx>();

    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)
            throws ProcessException {
        // straight execution, no thread pooling, we're already running in the parent process thread
        ProcessListener listener = new ProcessListener(
                new ExecutionStatus(processName, executionId, ProcessState.RUNNING, 0, null));
        ProcessFactory pf = GeoServerProcessors.createProcessFactory(processName);
        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);
        if (listener.exception != null) {
            throw new ProcessException("Process failed: " + listener.exception.getMessage(), listener.exception);
        }
        return result;
    }

    @Override
    public void submit(String executionId, Name processName, Map<String, Object> inputs, boolean background)
            throws ProcessException {
        ExecutionStatusEx status = new ExecutionStatusEx(processName, executionId);
        ProcessListener listener = new ProcessListener(status);
        status.listener = listener;
        ProcessCallable callable = new ProcessCallable(inputs, status);
        Future<Map<String, Object>> future;
        if (background) {
            future = asynchService.submit(callable);
        } else {
            future = synchService.submit(callable);
        }
        status.future = future;
        executions.put(executionId, status);
    }

    protected ExecutionStatusEx createExecutionStatus(Name processName, String executionId) {
        return new ExecutionStatusEx(processName, executionId);
    }

    @Override
    public ExecutionStatus getStatus(String executionId) {
        ExecutionStatusEx status = executions.get(executionId);
        if (status != null) {
            return status.getStatus();
        } else {
            return null;
        }
    }

    @Override
    public Map<String, Object> getOutput(String executionId, long timeout) throws ProcessException {
        ExecutionStatusEx status = executions.get(executionId);
        if (status == null) {
            return null;
        }
        try {
            if (timeout <= 0) {
                return status.future.get();
            } else {
                return status.future.get(timeout, TimeUnit.MILLISECONDS);
            }
        } catch (Exception e) {
            if (e instanceof ExecutionException && e.getCause() instanceof Exception) {
                e = (Exception) e.getCause();
            }
            if (e instanceof ProcessException) {
                throw (ProcessException) e;
            } else {
                throw new ProcessException("Process execution " + executionId + " failed", e);
            }
        } finally {
            // we're done
            executions.remove(executionId);
        }
    }

    @Override
    public void cancel(String executionId) {
        ExecutionStatusEx status = executions.get(executionId);
        if (status != null) {
            status.setPhase(ProcessState.CANCELLED);
            status.future.cancel(true);
            status.listener.setCanceled(true);
        }
    }

    @Override
    public List<ExecutionStatus> getRunningProcesses() {
        List<ExecutionStatus> result = new ArrayList<ExecutionStatus>();
        for (ExecutionStatusEx status : executions.values()) {
            result.add(status.getStatus());
        }
        return result;
    }

    @Override
    public int getPriority() {
        return ExtensionPriority.LOWEST;
    }

    class ProcessCallable implements Callable<Map<String, Object>> {

        Map<String, Object> inputs;

        ProcessListener listener;

        ExecutionStatusEx status;

        ThreadLocalsTransfer threadLocalTransfer;

        public ProcessCallable(Map<String, Object> inputs, ExecutionStatusEx status) {
            this.inputs = inputs;
            this.status = status;
            this.threadLocalTransfer = new ThreadLocalsTransfer();
        }

        @Override
        public Map<String, Object> call() throws Exception {
            try {
                // transfer the thread locals to this execution context
                threadLocalTransfer.apply();

                resourceManager.setCurrentExecutionId(status.getExecutionId());
                status.setPhase(ProcessState.RUNNING);
                ProcessListener listener = status.listener;
                Name processName = status.getProcessName();
                ProcessFactory pf = GeoServerProcessors.createProcessFactory(processName);
                if (pf == null) {
                    throw new WPSException("No such process: " + processName);
                }

                // execute the process
                Map<String, Object> result = null;
                try {
                    Process p = pf.create(processName);
                    result = p.execute(inputs, listener);
                    if (listener.exception != null) {
                        status.setPhase(ProcessState.FAILED);
                        throw new WPSException("Process failed: " + listener.exception.getMessage(),
                                listener.exception);
                    }
                    return result;
                } finally {
                    // update status unless cancelled
                    if (status.getPhase() == ProcessState.RUNNING) {
                        status.setPhase(ProcessState.COMPLETED);
                    }
                }
            } finally {
                // clean up the thread locals
                threadLocalTransfer.cleanup();
            }
        }

    }

    /**
     * A pimped up execution status
     * 
     * @author Andrea Aime - GeoSolutions
     */
    static class ExecutionStatusEx extends ExecutionStatus {

        Future<Map<String, Object>> future;

        ProcessListener listener;

        public ExecutionStatusEx(Name processName, String executionId) {
            super(processName, executionId, ProcessState.QUEUED, 0, null);
        }

        public ExecutionStatus getStatus() {
            return new ExecutionStatus(processName, executionId, phase, progress, task);
        }

        @Override
        public void setPhase(ProcessState phase) {
            if (phase == ProcessState.CANCELLED && getPhase() != ProcessState.CANCELLED) {
                future.cancel(true);
                listener.setCanceled(true);
            }
            super.setPhase(phase);
        }

        @Override
        public Map<String, Object> getOutput(long timeout) throws Exception {
            if (timeout <= 0) {
                return future.get();
            } else {
                return future.get(timeout, TimeUnit.MILLISECONDS);
            }
        }
    }

    /**
     * Listens to the process progress and allows to cancel it
     * 
     * @author Andrea Aime - GeoSolutions
     */
    static class ProcessListener implements ProgressListener {

        static final Logger LOGGER = Logging.getLogger(ProcessListener.class);

        ExecutionStatus status;

        InternationalString task;

        String description;

        Throwable exception;

        public ProcessListener(ExecutionStatus status) {
            this.status = status;
        }

        @Override
        public InternationalString getTask() {
            return task;
        }

        @Override
        public void setTask(InternationalString task) {
            this.task = task;
            if (task != null) {
                status.setTask(task.toString());
            }
        }

        @Override
        public String getDescription() {
            return this.description;
        }

        @Override
        public void setDescription(String description) {
            this.description = description;

        }

        @Override
        public void started() {
            status.setPhase(ProcessState.RUNNING);
        }

        @Override
        public void progress(float percent) {
            status.setProgress(percent);
        }

        @Override
        public float getProgress() {
            return status.getProgress();
        }

        @Override
        public void complete() {
            status.setPhase(ProcessState.COMPLETED);
        }

        @Override
        public void dispose() {
            // nothing to do
        }

        @Override
        public boolean isCanceled() {
            return status.getPhase() == ProcessState.CANCELLED;
        }

        @Override
        public void setCanceled(boolean cancel) {
            if (cancel == true) {
                status.setPhase(ProcessState.CANCELLED);
            }

        }

        @Override
        public void warningOccurred(String source, String location, String warning) {
            LOGGER.log(Level.WARNING,
                    "Got a warning during process execution " + status.getExecutionId() + ": " + warning);
        }

        @Override
        public void exceptionOccurred(Throwable exception) {
            this.exception = exception;
        }

    }

}