ModelDataService.java [src/csip] Revision: 7a86841f001737f68e418c03f22a9e305d68a475  Date: Mon Apr 04 10:48:16 MDT 2016
/*
 * $Id$
 * 
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * 2010-2013, Olaf David and others, Colorado State University.
 *
 * CSIP is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 2.1.
 *
 * CSIP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with OMS.  If not, see <http://www.gnu.org/licenses/lgpl.txt>.
 */
package csip;

import com.fasterxml.uuid.Generators;
import csip.annotations.*;
import csip.utils.*;
import csip.utils.Services.FormDataParameter;

import java.io.*;
import java.net.URI;
import java.sql.Connection;
import java.text.DateFormat;
import java.util.*;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import org.apache.commons.io.*;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.codehaus.jettison.json.*;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;

/**
 * Base class for all modeling and data services.
 *
 * A service implementation will subclass ModelDataService.
 *
 * @author Olaf David
 */
public abstract class ModelDataService {

    // Execution status
    public static final String EXEC_OK = null;
    public static final String EXEC_FAILED = "Error";

    // General const
    public static final String KEY_REPORT = "report";
    public static final String KEY_METAINFO = "metainfo";
    public static final String KEY_PARAMETER = "parameter";
    public static final String KEY_RESULT = "result";
    //
    // metainfo keys
    public static final String KEY_PARAMETERSETS = "parametersets";
    public static final String KEY_SUUID = "suid";
    public static final String KEY_STATUS = "status";
    public static final String KEY_NEXT_POLL = "next_poll";
    public static final String KEY_FIRST_POLL = "first_poll";
    public static final String KEY_CPU_TIME = "cpu_time";
    public static final String KEY_CLOUD_NODE = "cloud_node";
    public static final String KEY_SERVICE_URL = "service_url";
    public static final String KEY_URL = "url";
    public static final String KEY_REQ_IP = "request_ip";
    public static final String KEY_KEEP_RESULTS = "keep_results";
    public static final String KEY_EXPIRATION_DATE = "expiration_date";
    public static final String KEY_TSTAMP = "tstamp";  //?
    public static final String KEY_TZ = "tz";   // timezone
    public static final String KEY_PROGRESS = "progress";   // timezone

    // parameter keys
    public static final String KEY_NAME = "name";
    public static final String VALUE = "value";
    public static final String KEY_VALUE = "value";
    public static final String GEOMETRY = "geometry";
    public static final String KEY_UNIT = "unit";
    public static final String KEY_DESC = "description";
    public static final String KEY_TIME_FILEIO = "timeFileIO";
    public static final String KEY_TIME_MODEL = "timeModel";
    public static final String KEY_TIME_CLIMATE_QUERY = "timeClimateQuery";
    public static final String KEY_TIME_SOIL_QUERY = "timeSoilQuery";
    public static final String KEY_TIME_LOGGING = "timeLogging";
    public static final String KEY_TIME_TOTAL = "timeTotal";
    public static final String KEY_REQUEST_RESULTS = "request-results";
    //
    public static final String FORM_PARAM = "param";
    //
    public static final String ERROR = "error";
    public static final String OK = "ok";
    public static final String IN = "in";
    public static final String INTENT = "intent";
    public static final String MAX = "max";
    public static final String MIN = "min";
    public static final String OUT = "out";
    public static final String RANGE = "range";
    public static final String UNIT = "unit";

    // execution stages
    public static final String RUNNING = "Running";
    public static final String FINISHED = "Finished";
    public static final String CANCELED = "Canceled";
    public static final String FAILED = "Failed";
    public static final String UNKNOWN = "Unknown";
    public static final String SUBMITTED = "Submitted";

    // execution modes
    public static final String SYNC = "sync";
    public static final String ASYNC = "async";
    public static final String KEY_MODE = "mode";

    // Common report constants
    public static final String REPORT_FILE = "report.json";
    public static final String REQUEST_FILE = ".request.json";
    public static final String RESPONSE_FILE = ".response.json";
    public static final String LOG_FILE = ".log.txt";
    //
    public static final String REPORT_TYPE = "type";
    public static final String REPORT_VALUE = "value";
    public static final String REPORT_NAME = "name";
    public static final String REPORT_UNITS = "units";
    public static final String REPORT_DESC = "description";
    public static final String REPORT_DIM0 = "dimension0";
    public static final String REPORT_DIM = "dim";

    private final String suid = Generators.timeBasedGenerator().generate().toString();

//    protected final Logger LOG = Logger.getLogger(getClass().getAnnotation(Path.class).value());
    protected String tz = Config.getString("csip.timezone");
    //
    //
    private File workspace;
    private File results;
//    private File cache;

    //
    private JSONObject metainfo;
    private JSONObject request;
    private JSONArray param;
    private Map<String, JSONObject> paramMap;
    //
//    private String incoming_request;
    private String req_host;
    private String req_url;
    private String req_remoteIp;
    private String req_context;
    private String req_scheme;

    //TODO: make this private.
    public Task mt;

    private Date start = null;

    private String[] inputs = ModelSession.NO_ATTACHMENTS;  // no attachments

    private JSONArray res;
    private JSONArray rep;

    // The progress (message) during execution.
    private String progress = null;

    // file stuff
    private List<File> fres;
    private Map<File, String> fdesc;
    private List<File> frep;

    private static final String AUTO_RUN = "auto";

    // options
    private boolean unpack;

    protected SessionLogger LOG;

    /**
     * This is an error. (either an exception or a string).
     */
    Throwable serviceError = null;


    // report
    private JSONArray reportData() {
        if (rep == null) {
            rep = new JSONArray();
        }
        return rep;
    }


    private JSONArray results() {
        if (res == null) {
            res = new JSONArray();
        }
        return res;
    }


    private List<File> fresults() {
        if (fres == null) {
            fres = new ArrayList<>();
        }
        return fres;
    }


    private List<File> freports() {
        if (frep == null) {
            frep = new ArrayList<>();
        }
        return frep;
    }


    private Map<File, String> fdesc() {
        if (fdesc == null) {
            fdesc = new HashMap<>();
        }
        return fdesc;
    }

//    /**
//     * Get the cache directory.
//     *
//     * @return The file representing the cache folder.
//     */
//    private File getCacheDir() {
//        if (cache == null) {
//            cache = new File(Config.getString("csip.cache.dir", "/tmp/csip/cache"));
//            cache.mkdirs();
//            if (LOG.isLoggable(Level.INFO)) {
//                LOG.info(getServicePath() + " create cache dir: " + cache);
//            }
//        }
//        return cache;
//    }
//    /**
//     * Get a cached file.
//     *
//     * @param suid The suid of a previous service call.
//     * @param filename the file name
//     * @return the cached file or null if not exist.
//     * @throws Exception
//     */
//    private File getCachedFile(String suid, String filename) throws Exception {
//        File cacheFile = new File(getCacheDir(), suid + File.separator + filename);
//        for (int i = 0; i < 3; i++) {
//            if (cacheFile.exists()) {
//                if (LOG.isLoggable(Level.INFO)) {
//                    LOG.info("local cache -> workspace: " + suid + "/" + filename);
//                }
//                File wsFile = new File(getWorkspaceDir(), filename);
//                Files.createSymbolicLink(wsFile.toPath(), cacheFile.toPath());
////            FileUtils.copyFile(cacheFile, wsFile);
//                return wsFile;
//            }
//            byte[] data = Config.getCacheStore().getFile(suid, filename);
//            if (data == null) {
//                return null;
//            }
//
//            if (LOG.isLoggable(Level.INFO)) {
//                LOG.info("redis -> local cache: " + suid + "/" + filename);
//            }
//            FileUtils.writeByteArrayToFile(cacheFile, data);
////            Files.write(cacheFile.toPath(), data);
//        }
//        return null;
//    }
//    /**
//     * Add a file to the cache
//     *
//     * @param file the file to cache
//     * @param ttlsec the time to cache it in seconds.
//     * @throws Exception If the file does not exists.
//     */
//    private void setCachedFile(File file, int ttlsec) throws Exception {
//        if (!file.exists()) {
//            throw new IllegalArgumentException("Not found " + file);
//        }
////        byte[] filecontent = FileUtils.readFileToByteArray(file);
//        byte[] filecontent = Files.readAllBytes(file.toPath());
//        Config.getCacheStore().putFile(suid, file.getName(), filecontent, file.length(), ttlsec);
//    }
    //    private void handleCache(JSONObject tmp_metainfo) throws Exception {
//        if (tmp_metainfo.has("cache")) {
//            // fill the cache
//            String c = tmp_metainfo.getString("cache");
//            String[] cinfo = c.split("\\s+");
//            if (cinfo.length < 2) {
//                throw new ServiceException("Invalid cache request: " + c);
//            }
//            int ttlsec = 300;
//            try {
//                ttlsec = Integer.parseInt(cinfo[0]);
//            } catch (NumberFormatException E) {
//                throw new ServiceException("Invalid cache ttl value: " + ttlsec);
//            }
//            for (int i = 1; i < cinfo.length; i++) {
//                String file = cinfo[i];
//                // files should be in the workspace by now
//                File f = new File(getWorkspaceDir(), file);
//                if (!f.exists()) {
//                    throw new ServiceException("Not found for caching: " + f.toString());
//                }
//                setCachedFile(f, ttlsec);
//                LOG.log(Level.INFO, "Cached  " + f + " for " + ttlsec + " seconds.");
//            }
//        } else if (tmp_metainfo.has("cached")) {
//            // fetch the cache
//            String c = tmp_metainfo.getString("cached");
//            String[] cinfo = c.split("\\s+");
//            if (cinfo.length < 2) {
//                throw new ServiceException("Invalid cache request: " + c);
//            }
//            String c_suid = cinfo[0];
//            for (int i = 1; i < cinfo.length; i++) {
//                String file = cinfo[i];
//                File f = getCachedFile(c_suid, file);
//                if (f == null) {
//                    throw new ServiceException("failed to fetch from cache: " + file);
//                }
//            }
//        }
//    }
///////////////////////////////////////////////////////////////////// URL/ Paths    

    /**
     * The request ip
     * @return the request ip
     */
    protected final String getRemoteAddr() {
        return req_remoteIp;
    }


    /**
     * Get the codebase without the model service path
     * @return the codebase of the URL as String
     */
    protected final String getCodebase() {
        String m = getServicePath();
        String u = getRequestURL();
        return u.substring(0, u.indexOf(m));
    }


    /**
     * Provide the service path name, e.g. 'weps/1.0'
     * @return the model service path
     */
    protected final String getServicePath() {
        Path p = getClass().getAnnotation(Path.class);
        if (p != null) {
            return p.value();
        }
        throw new RuntimeException("@Path annotation missing for " + getClass());
    }


    /**
     * Get the complete request URL
     * @return the full request URL
     */
    protected final String getRequestURL() {
        return req_url;
    }


    /**
     * Get the complete request URL
     * @return the full request URL
     */
    protected final String getRequestHost() {
        return req_host;
    }


    /**
     * Get the webapp context name.
     * @return the context name
     */
    protected final String getRequestContext() {
        return req_context;
    }


    /**
     * Get the webapp context name.
     * @return the context name
     */
    protected final String getRequestScheme() {
        return req_scheme;
    }


    /**
     * Indicate if the service needs a workspace folder (as sandbox)
     * @return true if it is needed, false otherwise.
     */
    protected final boolean hasWorkspaceDir() {
        return workspace != null;
    }


    /**
     * Indicate if the service needs a separate result folder.
     * @return true if it is needed, false otherwise.
     */
    private boolean hasResultsDir() {
        return results != null;
    }

////////////////////////////////////////////////////////////////////// Lifecycle    

    /**
     * workflow step 1: process the request data.
     * @throws Exception
     */
    protected void preProcess() throws Exception {
    }


    /**
     * workflow step 2: The process method.
     * @throws Exception
     */
    protected void doProcess() throws Exception {
    }


    /**
     * workflow step 3: create the response the data.
     * @throws Exception
     */
    protected void postProcess() throws Exception {
    }

    ////////////////////////////////////////////////////////////////////    

    private Throwable preProcess0() throws Exception {
        // handle static data
        for (Resource r : Binaries.getMergedResources(getClass())) {
            if (r.type() == ResourceType.JDBC || r.type() == ResourceType.REFERENCE || r.type() == ResourceType.OUTPUT) {
                continue;
            }
            File csip_home = new File(Config.getString("csip.dir"));
            if (!new File(csip_home, r.file()).exists()) {
                csip_home.mkdirs();
                File resource = Binaries.unpackResource(r.file(), csip_home);
                if (r.file().endsWith("zip") && r.type() == ResourceType.ARCHIVE) {
                    ZipFiles.unzip(resource);
                }
            }
        }
        return doProcessWrapper(pre);
    }


//    /**
//     * Create a callable model run. The callable is returning a string result.
//     * The string is 'null' if the model execution succeeded. It is not 'null'
//     * if there is an error and the string may contain the error message.
//     *
//     * @return a callable
//     * @throws Exception if an error occurred during execution.
//     */
    @Deprecated
    public Callable<Throwable> createCallable() throws Exception {
        return () -> {
            return process0();
        };
    }


    /**
     * Process logic of the service.
     * @return null if the process ended successfully, the error message
     * otherwise.
     * @throws Exception
     * @deprecated use doProcess() instead.
     */
    @Deprecated
    protected String process() throws Exception {
        return EXEC_OK;
    }

    private static final int pre = 0;
    private static final int proc = 1;
    private static final int post = 2;


    /**
     * This replaces the process() call from process0
     */
    private Throwable doProcessWrapper(int phase) {
        try {
            switch (phase) {
                case pre:
                    preProcess();
                    break;
                case proc:
                    doProcess();  // preferred
                    String ret = process(); // deprecated
                    if (ret != null) {
                        return new ServiceException(ret);
                    }
                    break;
                case post:
                    postProcess();
                    break;
            }
        } catch (Throwable E) {
            return E;
        }
        return null;
    }


    private void processOptions() {
        // fetching the defaults
        try {
            unpack = (boolean) Options.class.getMethod("unpackinput").getDefaultValue();
        } catch (Exception ex) {
            LOG.log(Level.SEVERE, null, ex);
        }
        Options opt = getClass().getAnnotation(Options.class);
        if (opt != null) {
            // overwritten.
            unpack = opt.unpackinput();
        }
    }


    private Throwable process0() throws Exception {
        Resource[] resources = Binaries.getResourcesByType(getClass(), ResourceType.OMS_COMP);
        if (resources.length > 0) {
            Class cl = getClass();
            if (!resources[0].file().isEmpty()) {
                String classname = resources[0].file();
                cl = Thread.currentThread().getContextClassLoader().loadClass(classname);
            }
            try {
                OMSComponentMapper cm = new OMSComponentMapper(cl.newInstance());
                cm.setInputs(this);
                cm.process();
                cm.getOutputs(this);
            } catch (Throwable T) {
                return T;
            }
            return null;
        }

        Resource resource = Binaries.getResourceById(getClass(), AUTO_RUN);
        if (resource == null) {
            return doProcessWrapper(proc);
        }

        Executable e = null;
        if (resource.type() == ResourceType.EXECUTABLE) {
            e = Binaries.getResourceExe0(getClass(), AUTO_RUN, getWorkspaceDir(), LOG);
        } else if (resource.type() == ResourceType.CLASSNAME || resource.type() == ResourceType.OMS_DSL) {
            List<File> jars = new ArrayList<>();
            File csip_home = new File(Config.getString("csip.dir", "/tmp/csip"));
            Resource[] r = Binaries.getResourcesByType(getClass(), ResourceType.JAR);
            for (Resource j1 : r) {
                // be aware of the underscore in a jar file.
                jars.add(Binaries.unpackResource(j1.file(), csip_home)); //     Binaries.unpackResource("/oms-all.jar_", new File(oms_home, "oms-all.jar"));
            }
            if (resource.type() == ResourceType.OMS_DSL) {
                e = Binaries.getResourceOMSDSL(getClass(), AUTO_RUN, getWorkspaceDir(), jars, LOG);
            } else {
                e = Binaries.getResourceJava(getClass(), AUTO_RUN, getWorkspaceDir(), jars, LOG);
            }
        }
        LOG.info("running : " + e.getName());
        int ret = e.exec();
        LOG.info("done with exit value: " + ret);

        if (ret == 0) {
            return null;
        }
        FilenameFilter ff = new WildcardFileFilter("*" + Binaries.STDERR, IOCase.INSENSITIVE);
        File[] f = getWorkspaceDir().listFiles(ff);
        if (f != null && f.length > 0) {
            return new ServiceException(FileUtils.readFileToString(f[0]));
        }
        return new ServiceException(EXEC_FAILED + " return code " + ret);
    }


    private Throwable postProcess0() throws Exception {
        for (Resource r : Binaries.getResourcesByType(getClass(), ResourceType.OUTPUT)) {
            String[] files = r.file().split("\\s+");
            FilenameFilter ff = new WildcardFileFilter(files, IOCase.INSENSITIVE);
            putResult(getWorkspaceDir().listFiles(ff));
        }
        return doProcessWrapper(post);
    }


    /**
     * Step 4: Create the results as JSON, (deprecated)
     * @return the results as an array of JSON objects.
     * @throws Exception
     * @deprecated replaced by {@link #postProcess()}
     */
    @Deprecated
    protected JSONArray createResults() throws Exception {
        return null;
    }


    /**
     * Create a report.
     * @return The report content as JSONArray
     * @throws Exception
     * @deprecated replaced by {@link #report()}
     */
    @Deprecated
    protected JSONArray createReport() throws Exception {
        return null;
    }


    /**
     * Create a report.
     *
     * @throws Exception
     */
    protected void report() throws Exception {
    }


    /**
     * Return the recommended polling interval for async calls in milliseconds.
     * @return the polling interval value in milliseconds
     */
    protected long getNextPoll() {
        Polling p = getClass().getAnnotation(Polling.class);
        if (p != null) {
            return p.next();
        }
        return -1;
    }


    /**
     * Get the time until first poll for async calls in milliseconds.
     * @return time to first poll in milliseconds
     */
    protected long getFirstPoll() {
        Polling p = getClass().getAnnotation(Polling.class);
        if (p != null) {
            return p.first();
        }
        return -1;
    }


    /**
     * Get a new Workspace folder for this model run. returns null if it has no
     * simulation folder.
     * @return the workspace or null if there should be none.
     */
    protected final File getWorkspaceDir() {
        if (workspace == null) {
            workspace = Services.getWorkDir(suid);
            workspace.mkdirs();
//            if (LOG.isLoggable(Level.INFO)) {
//                LOG.info("create workspace dir: " + workspace);
//            }
        }
        return workspace;
    }


    /**
     * Get a new Results folder for this model run. returns null if it has no
     * results folder.
     * @return the results folder or null if there should be none.
     */
    private File getResultsDir() {
        if (results == null) {
            results = Services.getResultsDir(suid);
            results.mkdirs();
//            if (LOG.isLoggable(Level.INFO)) {
//                LOG.info("create results dir: " + results);
//            }
        }
        return results;
    }


    /**
     * Get the simulation unique identifier (128 byte UUID)
     * @return the suid string
     */
    protected final String getSUID() {
        return suid;
    }


    /**
     * Get the request metainfo.
     * @return the metainfo
     */
    protected final JSONObject getMetainfo() {
        return metainfo;
    }


    /**
     * Get the request parameter
     * @return request parameter
     */
    protected final JSONArray getParam() {
        return param;
    }


    /**
     * Set the request metainfo.
     * @param mi
     * @deprecated
     */
    public final void setMetainfo(JSONObject mi) {
        metainfo = mi;
    }


    /**
     * Set the request parameter.
     * @param parameter
     * @deprecated
     */
    public void setParam(JSONArray parameter) {
        param = parameter;
    }


    /**
     * Set the request
     * @param req
     * @deprecated
     */
    public final void setRequest(JSONObject req) {
        request = req;
    }


    /**
     * Set the Parameter map
     * @deprecated
     */
    public final void setParamMap(Map<String, JSONObject> pm) {
        paramMap = pm;
    }


    /**
     * Get the Parameter as map "name" -> JSONObject
     * @return the parameter map
     */
    protected final Map<String, JSONObject> getParamMap() {
        return paramMap;
    }


    /**
     * Get the original JSOn request object.
     * @return the request object.
     */
    protected final JSONObject getRequest() {
        return request;
    }


    /////////////////////////////////////////////////////////////////// metainfo 
    /**
     * Get the metainfo value as String.
     * @param name the name of the metainfo entry
     * @return the value of a metainfo entry
     * @throws ServiceException
     */
    protected String getStringMetainfo(String name) throws ServiceException {
        try {
            return getMetainfo().getString(name);
        } catch (JSONException ex) {
            throw new ServiceException(ex);
        }
    }


    /**
     * Get metainfo value as int.
     * @param name the name of the metainfo entry
     * @return the int value of a metainfo entry.
     * @throws ServiceException
     */
    protected int getIntMetainfo(String name) throws ServiceException {
        try {
            return getMetainfo().getInt(name);
        } catch (JSONException ex) {
            throw new ServiceException(ex);
        }
    }


    /**
     * Get the metainfo value as double.
     * @param name the name of the metainfo entry
     * @return the metainfo value.
     * @throws ServiceException
     */
    protected double getDoubleMetainfo(String name) throws ServiceException {
        try {
            return getMetainfo().getDouble(name);
        } catch (JSONException ex) {
            throw new ServiceException(ex);
        }
    }


    /**
     * Get a metainfo value as boolean
     * @param name the name of the metainfo entry
     * @return the metainfo value
     * @throws ServiceException
     */
    protected boolean getBooleanMetainfo(String name) throws ServiceException {
        try {
            return getMetainfo().getBoolean(name);
        } catch (JSONException ex) {
            throw new ServiceException(ex);
        }
    }


    /**
     * Check if a metainfo entry exists.
     * @param name the name of a metainfo name.
     * @return true if a metainfo entry exists, false otherwise
     */
    protected boolean hasMetainfo(String name) {
        return getMetainfo().has(name);
    }


    /**
     * Get all metainfo names.
     * @return the set of metainfo names.
     */
    protected Set<String> getMetainfoNames() {
        Set<String> s = new TreeSet<>();
        Iterator i = getMetainfo().keys();
        while (i.hasNext()) {
            s.add(i.next().toString());
        }
        return s;
    }


    /**
     * Get the number of metainfo entries.
     * @return the number of entries.
     */
    protected int getMetainfoCount() {
        return getMetainfo().length();
    }


    protected void setMetainfoWarning(String msg) throws ServiceException {
        try {
            getMetainfo().put("warning", msg);
        } catch (JSONException ex) {
            throw new ServiceException("Warning failed.");
        }
    }


    protected void appendMetainfoWarning(String msg) throws ServiceException {
        try {
            String prevmsg = JSONUtils.getJSONString(getMetainfo(), "warning", "");
            prevmsg += "|" + msg;
            getMetainfo().put("warning", prevmsg);
        } catch (JSONException ex) {
            throw new ServiceException("Warning failed.");
        }
    }

///////////////////////////////////////////////////////////////// files handling

    /**
     * Get the attached files for this request. This includes the request.
     * @return The set of files.
     */
    protected Set<File> getFileInputs() {
        Set<File> f = new TreeSet<>();
        for (String att : inputs) {
            File file = new File(getWorkspaceDir(), att);
            if (file.exists()) {
                f.add(file);
            }
        }
        return f;
    }


    /**
     * Get the number of attachments. This includes the request.
     * @return the number of files attached.
     */
    protected int getFileInputsCount() {
        return inputs.length;
    }


    /**
     * Check is a input file exists in the workspace.
     *
     * @param name the file name
     * @return true if the file exist, false otherwise
     * @throws ServiceException
     */
    protected boolean hasFileInput(String name) throws ServiceException {
        return getFileInput(name) != null;
    }


    /**
     * Get a file object for a given file name.
     * @param name the file name (no path)
     * @return the file object with its absolute file path within the workspace.
     * It returns null if the file is not input or does not exist.
     * @throws csip.ServiceException
     */
    protected File getFileInput(String name) throws ServiceException {
        if (!Arrays.asList(inputs).contains(name)) {
            return null;
        }
        File f = new File(getWorkspaceDir(), name);
        if (f.exists()) {
            return f;
        }
        throw new ServiceException("Missing File: " + name);
    }

///////////////////////////////////////////////////////////// parameter handling

    /**
     * Check if a parameter exists.
     * @param name the name of the parameter entry
     * @return true if present false otherwise.
     */
    protected boolean hasParam(String name) {
        return getParamMap().containsKey(name);
    }


    /**
     * Get the number of parameter.
     * @return the number of parameter
     */
    protected int getParamCount() {
        return getParamMap().size();
    }


    /**
     * Get all parameter names.
     * @return the set of names.
     */
    protected Set<String> getParamNames() {
        return getParamMap().keySet();
    }


    /**
     * Get a String parameter
     * @param name the parameter name
     * @return the parameter value as String
     * @throws ServiceException
     */
    protected String getStringParam(String name) throws ServiceException {
        try {
            return getParam(name).getString(VALUE);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a String parameter.
     * @param name the name of the parameter
     * @param def the default value if the parameter is missing
     * @return the value of the parameter
     * @throws ServiceException
     */
    protected String getStringParam(String name, String def) throws ServiceException {
        try {
            JSONObject p = getParamMap().get(name);
            return (p == null) ? def : p.getString(VALUE);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get an int parameter.
     * @param name the parameter name
     * @return the parameter value as int
     * @throws ServiceException
     */
    protected int getIntParam(String name) throws ServiceException {
        try {
            return getParam(name).getInt(VALUE);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a int parameter.
     * @param name the name of the parameter
     * @param def the default value if parameter does not exist
     * @return the int value of the parameter.
     * @throws ServiceException
     */
    protected int getIntParam(String name, int def) throws ServiceException {
        try {
            JSONObject p = getParamMap().get(name);
            return (p == null) ? def : p.getInt(VALUE);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get an double parameter
     * @param name the parameter name
     * @return the parameter value as double
     * @throws ServiceException
     */
    protected double getDoubleParam(String name) throws ServiceException {
        try {
            return getParam(name).getDouble(VALUE);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a double parameter.
     * @param name the name of the parameter
     * @param def the default value if parameter does not exist
     * @return the double value of the parameter
     * @throws ServiceException
     */
    protected double getDoubleParam(String name, double def) throws ServiceException {
        try {
            JSONObject p = getParamMap().get(name);
            return (p == null) ? def : p.getDouble(VALUE);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a boolean parameter.
     * @param name the parameter name.
     * @return the parameter value as boolean.
     * @throws ServiceException
     */
    protected boolean getBooleanParam(String name) throws ServiceException {
        try {
            return getParam(name).getBoolean(VALUE);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a Boolean parameter.
     * @param name the name of the parameter
     * @param def the default value.
     * @return the boolean value of the parameter.
     * @throws ServiceException
     */
    protected boolean getBooleanParam(String name, boolean def) throws ServiceException {
        try {
            JSONObject p = getParamMap().get(name);
            return (p == null) ? def : p.getBoolean(VALUE);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a long parameter.
     * @param name the parameter name.
     * @return the parameter value as long.
     * @throws ServiceException
     */
    protected long getLongParam(String name) throws ServiceException {
        try {
            return getParam(name).getLong(VALUE);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a Long parameter.
     * @param name the name of the parameter
     * @param def the default value if parameter does not exist
     * @return the long value of the parameter
     * @throws ServiceException
     */
    protected long getLongParam(String name, long def) throws ServiceException {
        try {
            JSONObject p = getParamMap().get(name);
            return (p == null) ? def : p.getLong(VALUE);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a JSONObject parameter.
     * @param name the parameter name.
     * @return the parameter value as JSONObject.
     * @throws ServiceException
     */
    protected JSONObject getJSONParam(String name) throws ServiceException {
        try {
            return getParam(name).getJSONObject(VALUE);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a Long parameter.
     * @param name the name of the parameter
     * @param def the default value if parameter does not exist
     * @return the JSONObject of the parameter
     * @throws ServiceException
     */
    protected JSONObject getJSONParam(String name, JSONObject def) throws ServiceException {
        try {
            JSONObject p = getParamMap().get(name);
            return (p == null) ? def : p.getJSONObject(VALUE);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a JSONArray parameter.
     * @param name the parameter name.
     * @return the parameter value as JSONObject.
     * @throws ServiceException
     */
    protected JSONArray getJSONArrayParam(String name) throws ServiceException {
        try {
            return getParam(name).getJSONArray(VALUE);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a Long parameter.
     * @param name the name of the parameter
     * @param def the default value if parameter does not exist
     * @return the JSONObject of the parameter
     * @throws ServiceException
     */
    protected JSONArray getJSONArrayParam(String name, JSONArray def) throws ServiceException {
        try {
            JSONObject p = getParamMap().get(name);
            return (p == null) ? def : p.getJSONArray(VALUE);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a int[] parameter.
     * @param name the parameter name.
     * @return the parameter value as JSONObject.
     * @throws ServiceException
     */
    protected int[] getIntArrayParam(String name) throws ServiceException {
        try {
            JSONArray a = getParam(name).getJSONArray(VALUE);
            return JSONUtils.toIntArray(a);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a int[] parameter.
     * @param name the name of the parameter
     * @param def the default value if parameter does not exist
     * @return the JSONObject of the parameter
     * @throws ServiceException
     */
    protected int[] getIntArrayParam(String name, int[] def) throws ServiceException {
        try {
            JSONObject p = getParamMap().get(name);
            return (p == null) ? def : JSONUtils.toIntArray(p.getJSONArray(VALUE));
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a boolean[] parameter.
     * @param name the parameter name.
     * @return the parameter value as JSONObject.
     * @throws ServiceException
     */
    protected boolean[] getBooleanArrayParam(String name) throws ServiceException {
        try {
            JSONArray a = getParam(name).getJSONArray(VALUE);
            return JSONUtils.toBooleanArray(a);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a boolean[] parameter.
     * @param name the name of the parameter
     * @param def the default value if parameter does not exist
     * @return the JSONObject of the parameter
     * @throws ServiceException
     */
    protected boolean[] getBooleanArrayParam(String name, boolean[] def) throws ServiceException {
        try {
            JSONObject p = getParamMap().get(name);
            return (p == null) ? def : JSONUtils.toBooleanArray(p.getJSONArray(VALUE));
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a long[] parameter.
     * @param name the parameter name.
     * @return the parameter value as JSONObject.
     * @throws ServiceException
     */
    protected long[] getLongArrayParam(String name) throws ServiceException {
        try {
            JSONArray a = getParam(name).getJSONArray(VALUE);
            return JSONUtils.toLongArray(a);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a long[] parameter.
     * @param name the name of the parameter
     * @param def the default value if parameter does not exist
     * @return the JSONObject of the parameter
     * @throws ServiceException
     */
    protected long[] getLongArrayParam(String name, long[] def) throws ServiceException {
        try {
            JSONObject p = getParamMap().get(name);
            return (p == null) ? def : JSONUtils.toLongArray(p.getJSONArray(VALUE));
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a String[] parameter.
     * @param name the parameter name.
     * @return the parameter value as JSONObject.
     * @throws ServiceException
     */
    protected String[] getStringArrayParam(String name) throws ServiceException {
        try {
            JSONArray a = getParam(name).getJSONArray(VALUE);
            return JSONUtils.toStringArray(a);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a String[] parameter.
     * @param name the name of the parameter
     * @param def the default value if parameter does not exist
     * @return the JSONObject of the parameter
     * @throws ServiceException
     */
    protected String[] getStringArrayParam(String name, String[] def) throws ServiceException {
        try {
            JSONObject p = getParamMap().get(name);
            return (p == null) ? def : JSONUtils.toStringArray(p.getJSONArray(VALUE));
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a double[] parameter.
     * @param name the parameter name.
     * @return the parameter value as JSONObject.
     * @throws ServiceException
     */
    protected double[] getDoubleArrayParam(String name) throws ServiceException {
        try {
            JSONArray a = getParam(name).getJSONArray(VALUE);
            return JSONUtils.toDoubleArray(a);
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get a double[] parameter.
     * @param name the name of the parameter
     * @param def the default value if parameter does not exist
     * @return the JSONObject of the parameter
     * @throws ServiceException
     */
    protected double[] getDoubleArrayParam(String name, double[] def) throws ServiceException {
        try {
            JSONObject p = getParamMap().get(name);
            return (p == null) ? def : JSONUtils.toDoubleArray(p.getJSONArray(VALUE));
        } catch (JSONException ex) {
            throw new ServiceException("No Value for " + name, ex);
        }
    }


    /**
     * Get the unit of a parameter.
     * @param name the parameter name
     * @return the unit as string, 'null' if there is none.
     * @throws ServiceException
     */
    protected String getParamUnit(String name) throws ServiceException {
        try {
            return getParam(name).getString(UNIT);
        } catch (JSONException ex) {
            throw new ServiceException("No unit for " + name);
        }
    }


    /**
     * Get the description of a parameter.
     * @param name the parameter name
     * @return the description as string, 'null' if there is none.
     * @throws ServiceException
     */
    protected String getParamDescr(String name) throws ServiceException {
        try {
            return getParam(name).getString(KEY_DESC);
        } catch (JSONException ex) {
            throw new ServiceException("No description for " + name);
        }
    }


    /**
     * Get the geometry of a parameter
     * @param name the name if the parameter
     * @return the geometry of a parameter
     * @throws ServiceException
     */
    protected JSONObject getParamGeometry(String name) throws ServiceException {
        try {
            return getParam(name).getJSONObject(GEOMETRY);
        } catch (JSONException ex) {
            throw new ServiceException("No geometry for " + name);
        }
    }


    /**
     * Get the full JSON parameter record.
     * @param name
     * @return the JSON record.
     */
    private JSONObject getParam(String name) throws ServiceException {
        JSONObject p = getParamMap().get(name);
        if (p == null) {
            throw new ServiceException("Parameter not found: '" + name + "'");
        }
        return p;
    }


    /**
     * Get a service resource file. Resources are defined as service
     * annotations.
     *
     * @param id the id of the resource.
     * @return the extracted file within the local file system.
     * @throws ServiceException
     * @see csip.annotations.Resource
     */
    protected File getResourceFile(String id) throws ServiceException {
        return Binaries.getResourceFile(getClass(), id);
    }


    /**
     * Get an service executable from a resource definition. Resources are
     * defined as service annotations.
     *
     * @param id the id of the resource
     * @return the ProcessExecution for that executable
     * @throws ServiceException
     * @see csip.annotations.Resource
     */
    protected Executable getResourceExe(String id) throws ServiceException {
        return Binaries.getResourceExe0(getClass(), id, getWorkspaceDir(), LOG);
    }


    /**
     * Get a JDBC connection from a resource definition.
     *
     * @param id the id of the resource
     * @return the JDBC connection.
     * @throws ServiceException
     * @see csip.annotations.Resource
     */
    protected Connection getResourceJDBC(String id) throws ServiceException {
        return Binaries.getRessourceJDBC(getClass(), id, LOG);
    }

//////////////////////////////////////////////////////////////////////// results

    /**
     * Provide a string as a result.
     * @param name the result name
     * @param val the value to store
     * @param unit the physical unit
     * @param descr a description
     */
    protected void putResult(String name, String val, String descr, String unit) {
        results().put(JSONUtils.dataUnitDesc(name, val, unit, descr));
    }


    /**
     * Provide a string as a result.
     * @param name the result name
     * @param val the value to store
     * @param descr a description
     */
    protected void putResult(String name, String val, String descr) {
        results().put(JSONUtils.dataUnitDesc(name, val, null, descr));
    }


    /**
     * Provide a string as a result.
     * @param name the result name
     * @param val the value to store
     */
    protected void putResult(String name, String val) {
        results().put(JSONUtils.dataUnitDesc(name, val, null, null));
    }


    /**
     * Provide an int as a result.
     * @param name the result name
     * @param val the value to store
     * @param unit the physical unit
     * @param descr a description
     */
    protected void putResult(String name, int val, String descr, String unit) {
        results().put(JSONUtils.dataUnitDesc(name, val, unit, descr));
    }


    /**
     * Provide an int as a result.
     * @param name the result name
     * @param val the value to store
     * @param descr a description
     */
    protected void putResult(String name, int val, String descr) {
        results().put(JSONUtils.dataUnitDesc(name, val, null, descr));
    }


    /**
     * Provide an int as a result.
     * @param name the result name
     * @param val the value to store
     */
    protected void putResult(String name, int val) {
        results().put(JSONUtils.dataUnitDesc(name, val, null, null));
    }


    /**
     * Provide a generic object as a result. Supports returning a JSONObject.
     * @param val the value to store
     * @deprecated
     */
    protected void putResult(Object val) {
        results().put(val);
    }


    /**
     * Provide a double as a result.
     * @param name the result name
     * @param val the value to store
     * @param unit the physical unit
     * @param descr a description
     */
    protected void putResult(String name, double val, String descr, String unit) {
        results().put(JSONUtils.dataUnitDesc(name, val, unit, descr));
    }


    /**
     * Provide a double as a result.
     * @param name the result name
     * @param val the value to store
     * @param descr
     */
    protected void putResult(String name, double val, String descr) {
        results().put(JSONUtils.dataUnitDesc(name, val, null, descr));
    }


    /**
     * Provide a double as a result.
     * @param name the result name
     * @param val the value to store
     */
    protected void putResult(String name, double val) {
        results().put(JSONUtils.dataUnitDesc(name, val, null, null));
    }


    /**
     * Provide a boolean as a result.
     * @param name the result name
     * @param val the value to store
     * @param unit the physical unit
     * @param descr a description
     */
    protected void putResult(String name, boolean val, String descr, String unit) {
        results().put(JSONUtils.dataUnitDesc(name, val, unit, descr));
    }


    /**
     * Provide a boolean as a result.
     * @param name the result name
     * @param val the value to store
     * @param descr a description
     */
    protected void putResult(String name, boolean val, String descr) {
        results().put(JSONUtils.dataUnitDesc(name, val, null, descr));
    }


    /**
     * Provide a boolean as a result.
     * @param name the result name
     * @param val the value to store
     */
    protected void putResult(String name, boolean val) {
        results().put(JSONUtils.dataUnitDesc(name, val, null, null));
    }


    /**
     * Provide an Object as a result.
     * @param name the result name
     * @param val the value to store
     * @param descr the description
     */
    protected void putResult(String name, Object val, String descr) {
        results().put(JSONUtils.dataUnitDesc(name, val, null, descr));
    }


    /**
     * Provide a JSONObject as a result.
     * @param name the result name
     * @param val the value to store
     */
    protected void putResult(String name, Object val) {
        results().put(JSONUtils.dataUnitDesc(name, val, null, null));
    }


    /**
     * Provide a boolean array as a result.
     * @param name the result name
     * @param val the value to store
     * @param unit the physical unit
     * @param descr a description
     */
    protected void putResult(String name, boolean[] val, String descr, String unit) {
        JSONArray val_ = JSONUtils.toArray(val);
        results().put(JSONUtils.dataUnitDesc(name, val_, unit, descr));
    }


    /**
     * Provide a boolean array as a result.
     * @param name the result name
     * @param val the value to store
     * @param descr a description
     */
    protected void putResult(String name, boolean[] val, String descr) {
        JSONArray val_ = JSONUtils.toArray(val);
        results().put(JSONUtils.dataUnitDesc(name, val_, null, descr));
    }


    /**
     * Provide a boolean array as a result.
     * @param name the result name
     * @param val the value to store
     */
    protected void putResult(String name, boolean[] val) {
        JSONArray val_ = JSONUtils.toArray(val);
        results().put(JSONUtils.dataUnitDesc(name, val_, null, null));
    }


    /**
     * Provide a double array as a result.
     * @param name the result name
     * @param val the value to store
     * @param unit the physical unit
     * @param descr a description
     */
    protected void putResult(String name, double[] val, String descr, String unit) {
        JSONArray val_ = JSONUtils.toArray(val);
        results().put(JSONUtils.dataUnitDesc(name, val_, unit, descr));
    }


    /**
     * Provide a double array as a result.
     * @param name the result name
     * @param val the value to store
     * @param descr a description
     */
    protected void putResult(String name, double[] val, String descr) {
        JSONArray val_ = JSONUtils.toArray(val);
        results().put(JSONUtils.dataUnitDesc(name, val_, null, descr));
    }


    /**
     * Provide a double array as a result.
     * @param name the result name
     * @param val the value to store
     */
    protected void putResult(String name, double[] val) {
        JSONArray val_ = JSONUtils.toArray(val);
        results().put(JSONUtils.dataUnitDesc(name, val_, null, null));
    }


    /**
     * Provide a int array as a result.
     * @param name the result name
     * @param val the value to store
     * @param unit the physical unit
     * @param descr a description
     */
    protected void putResult(String name, int[] val, String descr, String unit) {
        JSONArray val_ = JSONUtils.toArray(val);
        results().put(JSONUtils.dataUnitDesc(name, val_, unit, descr));
    }


    /**
     * Provide a int array as a result.
     * @param name the result name
     * @param val the value to store
     * @param descr a description
     */
    protected void putResult(String name, int[] val, String descr) {
        JSONArray val_ = JSONUtils.toArray(val);
        results().put(JSONUtils.dataUnitDesc(name, val_, null, descr));
    }


    /**
     * Provide a int array as a result.
     * @param name the result name
     * @param val the value to store
     */
    protected void putResult(String name, int[] val) {
        JSONArray val_ = JSONUtils.toArray(val);
        results().put(JSONUtils.dataUnitDesc(name, val_, null, null));
    }


    /**
     * Provide a long array as a result.
     * @param name the result name
     * @param val the value to store
     * @param unit the physical unit
     * @param descr a description
     */
    protected void putResult(String name, long[] val, String descr, String unit) {
        JSONArray val_ = JSONUtils.toArray(val);
        results().put(JSONUtils.dataUnitDesc(name, val_, unit, descr));
    }


    /**
     * Provide a long array as a result.
     * @param name the result name
     * @param val the value to store
     * @param descr a description
     */
    protected void putResult(String name, long[] val, String descr) {
        JSONArray val_ = JSONUtils.toArray(val);
        results().put(JSONUtils.dataUnitDesc(name, val_, null, descr));
    }


    /**
     * Provide a long array as a result.
     * @param name the result name
     * @param val the value to store
     */
    protected void putResult(String name, long[] val) {
        JSONArray val_ = JSONUtils.toArray(val);
        results().put(JSONUtils.dataUnitDesc(name, val_, null, null));
    }


    /**
     * Provide a String array as a result.
     * @param name the result name
     * @param val the value to store
     * @param unit the physical unit
     * @param descr a description
     */
    protected void putResult(String name, String[] val, String descr, String unit) {
        JSONArray val_ = JSONUtils.toArray(val);
        results().put(JSONUtils.dataUnitDesc(name, val_, unit, descr));
    }


    /**
     * Provide a String array as a result.
     * @param name the result name
     * @param val the value to store
     * @param descr a description
     */
    protected void putResult(String name, String[] val, String descr) {
        JSONArray val_ = JSONUtils.toArray(val);
        results().put(JSONUtils.dataUnitDesc(name, val_, null, descr));
    }


    /**
     * Provide a String array as a result.
     * @param name the result name
     * @param val the value to store
     */
    protected void putResult(String name, String[] val) {
        JSONArray val_ = JSONUtils.toArray(val);
        results().put(JSONUtils.dataUnitDesc(name, val_, null, null));
    }


    /**
     * Provide a file as a result. If the files represents a folder, it gets
     * added to the result as zip file.
     *
     * @param file the file to add as result.
     */
    protected void putResult(File file) {
        if (file == null || !file.exists() || !file.canRead()) {
            throw new IllegalArgumentException("Cannot acces File/Folder: " + file);
        }
        if (file.isDirectory()) {
            try {
                // can only add zipped folder as output.
                file = ZipFiles.zip(file);
            } catch (IOException ex) {
                LOG.log(Level.SEVERE, null, ex);
                return;
            }
        }
        fresults().add(file);
    }


    /**
     * Provide a file with description as a result.
     * @param file
     * @param descr
     */
    protected void putResult(File file, String descr) {
        putResult(file);
        fdesc().put(file, descr);
    }


    /**
     * Provide multiple files as a result.
     * @param files the files to add as a result
     */
    protected void putResult(File... files) {
        for (File file : files) {
            putResult(file);
        }
    }

///////////////////////////////////////////////////////////////////////// report

    /**
     * Put a String value into a report.
     * @param name the result name
     * @param val the value to store
     * @param unit the physical unit
     * @param descr a description
     */
    protected void putReport(String name, String val, String descr, String unit) {
        reportData().put(JSONUtils.dataUnitDesc(name, val, unit, descr));
    }


    /**
     * Put a String value into a report.
     * @param name the result name
     * @param val the value to store
     * @param descr a description
     */
    protected void putReport(String name, String val, String descr) {
        reportData().put(JSONUtils.dataUnitDesc(name, val, null, descr));
    }


    /**
     * Put a String value into a report.
     * @param name the result name
     * @param val the value to store
     */
    protected void putReport(String name, String val) {
        reportData().put(JSONUtils.dataUnitDesc(name, val, null, null));
    }


    /**
     * Put a int value into a report.
     * @param name the result name
     * @param val the value to store
     * @param unit the physical unit
     * @param descr a description
     */
    protected void putReport(String name, int val, String descr, String unit) {
        reportData().put(JSONUtils.dataUnitDesc(name, val, unit, descr));
    }


    /**
     * Put a int value into a report.
     * @param name the result name
     * @param val the value to store
     * @param descr a description
     */
    protected void putReport(String name, int val, String descr) {
        reportData().put(JSONUtils.dataUnitDesc(name, val, null, descr));
    }


    /**
     * Put a int value into a report.
     * @param name the result name
     * @param val the value to store
     */
    protected void putReport(String name, int val) {
        reportData().put(JSONUtils.dataUnitDesc(name, val, null, null));
    }


    /**
     * Put a double value into a report.
     * @param name the result name
     * @param val the value to store
     * @param unit the physical unit
     * @param descr a description
     */
    protected void putReport(String name, double val, String descr, String unit) {
        reportData().put(JSONUtils.dataUnitDesc(name, val, unit, descr));
    }


    /**
     * Put a double value into a report.
     * @param name the result name
     * @param val the value to store
     * @param descr a description
     */
    protected void putReport(String name, double val, String descr) {
        reportData().put(JSONUtils.dataUnitDesc(name, val, null, descr));
    }


    /**
     * Put a double value into a report.
     * @param name the result name
     * @param val the value to store
     */
    protected void putReport(String name, double val) {
        reportData().put(JSONUtils.dataUnitDesc(name, val, null, null));
    }


    /**
     * Put a boolean value into a report.
     * @param name the result name
     * @param val the value to store
     * @param unit the physical unit
     * @param descr a description
     */
    protected void putReport(String name, boolean val, String descr, String unit) {
        reportData().put(JSONUtils.dataUnitDesc(name, val, unit, descr));
    }


    /**
     * Put a boolean value into a report.
     * @param name the result name
     * @param val the value to store
     * @param descr a description
     */
    protected void putReport(String name, boolean val, String descr) {
        reportData().put(JSONUtils.dataUnitDesc(name, val, null, descr));
    }


    /**
     * Put a boolean value into a report.
     * @param name the result name
     * @param val the value to store
     */
    protected void putReport(String name, boolean val) {
        reportData().put(JSONUtils.dataUnitDesc(name, val, null, null));
    }


    /**
     * Put a JSONObject value into a report.
     * @param name the result name
     * @param val the value to store
     * @param unit the physical unit
     * @param descr a description
     */
    protected void putReport(String name, Object val, String descr, String unit) {
        reportData().put(JSONUtils.dataUnitDesc(name, val, unit, descr));
    }


    /**
     * Put a JSONObject value into a report.
     * @param name the result name
     * @param val the value to store
     * @param descr a description
     */
    protected void putReport(String name, Object val, String descr) {
        reportData().put(JSONUtils.dataUnitDesc(name, val, null, descr));
    }


    /**
     * Put a JSONObject value into a report.
     * @param name the result name
     * @param val the value to store
     */
    protected void putReport(String name, Object val) {
        reportData().put(JSONUtils.dataUnitDesc(name, val, null, null));
    }


    /**
     * Put a File into a report. If the argument is a folder, it gets zipped and
     * put into the report as zipped file.
     *
     * @param file the file to report
     */
    protected void putReport(File file) {
        if (file == null || !file.exists() || !file.canRead()) {
            throw new IllegalArgumentException("Cannot acces File/Folder: " + file);
        }
        if (file.isDirectory()) {
            try {
                // can only add zipped folder as report.
                file = ZipFiles.zip(file);
            } catch (IOException ex) {
                LOG.log(Level.SEVERE, null, ex);
                return;
            }
        }
        freports().add(file);
    }


    /**
     * Put a File into a report.
     * @param file the file
     * @param descr a description
     */
    protected void putReport(File file, String descr) {
        putReport(file);
        fdesc().put(file, descr);
    }


    /**
     * Put Files into a report.
     * @param file the files to report
     */
    protected void putReport(File... files) {
        for (File file : files) {
            putReport(file);
        }
    }

///////////////////////////////////////////////////////////////////////private     

    private void doCreateReport(JSONObject meta) throws Exception {
        JSONArray report = createReport();
        if (report == null) {
            report = rep;
        }
        if (report != null) {
            if (frep != null) {
                File[] reportfiles = frep.toArray(new File[0]);
                annotateResults(report, reportfiles);
            }

            // put an flag into the metainfo to indicate report 
            // availability
            meta.put("report", true);

            JSONObject r = new JSONObject();
            r.put(KEY_METAINFO, meta);
            r.put(KEY_REPORT, report);
            FileUtils.writeStringToFile(new File(getResultsDir(), REPORT_FILE), r.toString(2));
            if (LOG.isLoggable(Level.INFO)) {
                LOG.info("created report : " + new File(getResultsDir(), REPORT_FILE));
            }
        }
    }


    private void processResults(File[] resultfiles) throws IOException {
        File wsDir = getWorkspaceDir();
        File resultDir = getResultsDir();
        for (File sfile : resultfiles) {
            if (!sfile.exists()) {
                sfile = new File(wsDir, sfile.toString());
                if (!sfile.exists()) {
                    throw new IOException("Result file not found: " + sfile);
                }
            }

            if (!sfile.canRead()) {
                throw new IOException("Cannot read file: " + sfile);
            }
            if (LOG.isLoggable(Level.INFO)) {
                LOG.info("Copy result" + sfile.toString() + " to " + resultDir);

            }
            FileUtils.copyFileToDirectory(sfile, resultDir);
        }
    }


    private void annotateResults(JSONArray results, File[] files) throws Exception {
        URI cb = Services.toPublicURL(new URI(getCodebase()));
        String url = cb.toString();
        for (File f : files) {
            String fn = f.getName();
            String descr = fdesc().get(f);
            results.put(JSONUtils.dataDesc(fn, url + "q/" + suid + "/" + fn, descr));
        }
    }


    private JSONObject getFailedMetaInfo() {
        try {
            JSONObject metainfo_clone = JSONUtils.clone(metainfo);
            metainfo_clone.put(KEY_STATUS, FAILED);
            metainfo_clone.put(KEY_SUUID, suid);
            metainfo_clone.put(KEY_TSTAMP, Dates.nowISO(tz));
            metainfo_clone.put(KEY_SERVICE_URL, req_url);
            metainfo_clone.put(KEY_CLOUD_NODE, Services.LOCAL_IP_ADDR);
            metainfo_clone.put(KEY_REQ_IP, req_remoteIp);
            String reqContext = getRequestContext().substring(1);
            String v = Config.getString(reqContext + ".version");
            if (v != null) {
                metainfo_clone.put(reqContext + ".version", v);
            }
            metainfo_clone.put("csip.version", Config.getString("csip.version"));
            return metainfo_clone;
        } catch (JSONException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }
        return null;
    }

//////////////////////////////////////////////////////////////////// HTTP    

    /**
     * Describe the service as JSON. (Service endpoint only)
     * @return The service signature as JSON
     */
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public final String describeJSON() {
        Resource[] resource = Binaries.getResourcesByType(getClass(), ResourceType.OMS_COMP);
        if (resource.length > 0) {
            try {
                Class cl = getClass();
                if (!resource[0].file().isEmpty()) {
                    String classname = resource[0].file();
                    cl = Thread.currentThread().getContextClassLoader().loadClass(classname);
                }
                Object comp = cl.newInstance();
                OMSComponentMapper cm = new OMSComponentMapper(comp);
                return cm.getInputs().toString(4);
            } catch (Exception ex) {
                Logger.getLogger(ModelDataService.class.getName()).log(Level.SEVERE, null, ex);
                return JSONUtils.newRequest().toString();
            }
        }

        String classname = '/' + getClass().getName().replace('.', '/');
        try {
            InputStream is = getClass().getResourceAsStream(classname + "Req.json");
            if (is == null) {
                is = getClass().getResourceAsStream(classname + ".json");
                if (is == null) {
                    return JSONUtils.newRequest().toString(2);
                }
            }
            return new JSONObject(IOUtils.toString(is)).toString(2);
        } catch (IOException | JSONException ex) {
            return JSONUtils.newRequest().toString();
        }
    }


    /**
     * Service Handler for non-multipart requests. There are no form parameter,
     * everything is in the body. (Service endpoint only)
     * @param uriInfo The UriInfo context
     * @param req tye servlet request
     * @param requestStr the request string
     * @return the JSON response of the service.
     */
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public final String execute(@Context UriInfo uriInfo, @Context HttpServletRequest req, String requestStr) {
        if (start == null) {
            start = new Date();
            processOptions();
            LOG = new SessionLogger(getResultsDir(), getServicePath(), getSUID());
        }
        if (LOG.isLoggable(Level.INFO)) {
            LOG.info("path: " + getServicePath());
            LOG.info("from: " + req.getRemoteAddr() + "," + req.getRemoteHost() + "," + req.getRemoteUser());
            int max = Config.getInt("csip.logging.strmax", -1);
            if (max > 0) {
                int len = Math.min(max, requestStr.length());
                LOG.info("request: " + requestStr.substring(0, len) + " ... (truncated)");
            } else {
                LOG.info("request: " + requestStr);
            }
            LOG.info("request url: " + req.getRequestURL().toString());
            LOG.info("context: " + req.getContextPath());
            LOG.info("x-forwarded-for:" + req.getHeader("X-Forwarded-For"));
        }
        req_remoteIp = req.getHeader("X-Forwarded-For");
        if (req_remoteIp == null) {
            req_remoteIp = req.getRemoteAddr();
        }
        req_host = req.getRemoteHost();
        req_url = req.getRequestURL().toString();
        req_context = req.getContextPath();
        req_scheme = req.getScheme();

        JSONObject response = null;
        try {
            // extract all Ressources
            Binaries.extractAllResources(getClass(), LOG);

            if (requestStr == null) {
                throw new ServiceException("null request.");
            }

            request = new JSONObject(requestStr);
            FileUtils.writeStringToFile(new File(getResultsDir(), REQUEST_FILE), request.toString(2).replace("\\/", "/"));

            // get parameter and meta info.
            param = request.getJSONArray(KEY_PARAMETER);
            if (request.has(KEY_METAINFO)) {
                metainfo = request.getJSONObject(KEY_METAINFO);
            } else {
                metainfo = new JSONObject();
            }
            paramMap = JSONUtils.preprocess(param);

            // add the attachmed inputs to it
            if (inputs != null && inputs.length > 0) {
                metainfo.put("attachments", "\"" + Arrays.toString(inputs) + "\"");
            }

            // handleCache(metainfo);
            tz = JSONUtils.getJSONString(metainfo, KEY_TZ, Config.getString("csip.timezone"));
            if (LOG.isLoggable(Level.INFO)) {
                LOG.info("timezone: " + tz);
            }

            if (metainfo.has(KEY_PARAMETERSETS)) {
                // ensemble run
                response = new Callable<JSONObject>() {
                    String path = getServicePath();


                    @Override
                    public JSONObject call() throws Exception {

                        // map the request to callables
                        List<Callable<JSONObject>> ens = Services.mapEnsemble(request, path);
                        if (ens != null) {
                            // execute it
                            List<Future<JSONObject>> results = Services.runEnsemble(ens);

                            // reduce results
                            return Services.reduceEnsemble(results, request);
                        } else {
                            return JSONUtils.newError(getFailedMetaInfo(), param, new ServiceException("Not an ensemble."));
                        }
                    }

                }.call();
            } else {
                // single run:
                // step 1 preprocess input
//                preprocess();
                serviceError = preProcess0();
                if (serviceError != null) {
                    throw serviceError;
                }

                // step 2 create the model
                Callable<Throwable> model = createCallable();
//                Callable<Object> model = () -> {
//                    return process0();
//                };
                mt = new Task(model);

                if (metainfo.has(KEY_MODE) && metainfo.getString(KEY_MODE).equals(ASYNC)) {
                    // async call
                    JSONObject metainfo_clone = JSONUtils.clone(metainfo);

                    // Put polling info in obj before starting!
                    if (getNextPoll() != -1) {
                        metainfo_clone.put(KEY_NEXT_POLL, getNextPoll());
                    }
                    if (getFirstPoll() != -1) {
                        metainfo_clone.put(KEY_FIRST_POLL, getFirstPoll());
                    }

                    // generate response.
                    metainfo_clone.put(KEY_STATUS, SUBMITTED);
                    metainfo_clone.put(KEY_SUUID, suid);
                    metainfo_clone.put(KEY_TSTAMP, Dates.nowISO(tz));
                    metainfo_clone.put(KEY_SERVICE_URL, req_url);

                    // done, there is the response
                    response = JSONUtils.newResponse(request, null, metainfo_clone);
                    if (LOG.isLoggable(Level.INFO)) {
                        LOG.info("async run :" + response);
                    }

                    // start the async call as the very last thing before returning
                    mt.start();
                } else {
                    // sync call
                    mt.run();
                    response = new JSONObject(FileUtils.readFileToString(new File(getResultsDir(), RESPONSE_FILE)));
                }
            }
        } catch (Throwable T) {
            LOG.log(Level.SEVERE, "ERROR:", T);
            response = JSONUtils.newError(getFailedMetaInfo(), param, T);
        }
        LOG.close();
        try {
            return response.toString(2).replace("\\/", "/");
        } catch (JSONException E) {
            return response.toString().replace("\\/", "/");
        }
    }


    /**
     * Handler for model services. Multi-part handling. (Service endpoint only)
     * @param uriInfo the context info
     * @param httpReq the servlet request
     * @param multipart multi part input.
     *
     * @return the JSON response as String.
     */
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public final String execute(@Context UriInfo uriInfo, @Context HttpServletRequest httpReq, FormDataMultiPart multipart) {
        LOG = new SessionLogger(getResultsDir(), getServicePath(), getSUID());
        processOptions();
        start = new Date();
        String response = null;
        try {
            if (multipart == null || multipart.getBodyParts() == null || multipart.getBodyParts().isEmpty()) {
                throw new ServiceException("Missing JSON request and/or files");
            }
            Map<String, FormDataParameter> m = Services.getFormParameter(multipart.getBodyParts());
            File ws = getWorkspaceDir();

            // copy all file - file9 to workspace.
            inputs = Services.copyAndExtract(LOG, ws, m, unpack);

            String requestStr = null;
            // get param JSON
            FormDataParameter fdParam = m.get(FORM_PARAM);
            if (fdParam == null && !m.isEmpty()) {
                // only files are attached, no json
                requestStr = JSONUtils.newRequest().toString();
                if (LOG.isLoggable(Level.INFO)) {
                    LOG.info("creating empty request string");
                }
//                throw new ServiceException("Missing JSON request.");
            } else if (fdParam.getFilename() == null) {
                // direct passing
                requestStr = fdParam.getValue();
            } else {
                if (ws == null) {
                    throw new ServiceException("Illegal service configuration. missing simulation dir.");
                }
                File paramFile = new File(ws, fdParam.getFilename());
                if (!paramFile.exists()) {
                    throw new ServiceException("Missing JSON request file.");
                }
                requestStr = FileUtils.readFileToString(paramFile);
            }
            // proxy to the regular call.
            response = execute(uriInfo, httpReq, requestStr);
        } catch (Throwable T) {
            LOG.log(Level.SEVERE, "ERROR:", T);
            response = JSONUtils.newError(getFailedMetaInfo(), param, T).toString();
        }
        // Let's not catch every exception so we have hope of finding the bug!
        LOG.close();
        return response.replace("\\/", "/");
    }


    /**
     * Set the progress as a string message. Call this message during process()
     * to indicate progress for long running models. If the service is called
     * asynchronously the message will be reported in the metainfo part of the
     * rest call as the 'progress' entry.
     *
     * @param progress a meaningful message
     * @throws ServiceException
     */
    protected void setProgress(String progress) throws ServiceException {
        this.progress = progress;
        // this is just only updating the progess number without touching 
        // anything else.
        Config.SessionStore session = Config.getSessionStore();
        try {
            ModelSession s = session.getSession(suid);
            s.setProgress(progress);
            session.setSession(suid, s);
        } catch (Exception E) {
            throw new ServiceException(E);
        }
    }


    /**
     * Set the progress as a numerical value (0..100)
     * @param progress a value between 0 and 100;
     * @throws ServiceException
     */
    protected void setProgress(int progress) throws ServiceException {
        if (progress < 0 || progress > 100) {
            throw new ServiceException("invalid progress: " + progress);
        }
        setProgress(Integer.toString(progress));

    }

    /**
     * Model execution Task.
     *
     */
    public final class Task extends Thread {

        static final long ONE_DAY = 60 * 60 * 24 * 1000;  // 1 day in milli seconds, max time for a running job to be accessible.
        //
        FutureTask<Throwable> task;
        String status = UNKNOWN;
        //
        long keepResults = Dates.getDurationSec("csip.session.ttl");


        public Task(Callable<Throwable> call) {
            task = new FutureTask<>(call);
        }


        @Override
        public void run() {
            ExecutorService es = Config.getExecutorService();
            File[] resultfiles = null;
            try {
                es.submit(task);
                Config.getModelTasks().add(this);
                setRunningStatus();
                serviceError = task.get();
                if (serviceError == null) {
                    // step 3
//                    resultfiles = postprocess();
                    serviceError = postProcess0();

                    if (serviceError == null) {
                        // step 4
                        JSONArray results = createResults();
                        if (results == null) {
                            results = results();
                        }

                        if (resultfiles == null) {
                            resultfiles = fresults().toArray(new File[0]);
                        }

                        // automatically annotate the output if there are files being returned.
                        if (resultfiles != null && resultfiles.length > 0) {
                            annotateResults(results, resultfiles);
                        }

                        JSONObject meta = setFinishedStatus(results);

                        // step 5 create a report (if supported)
                        doCreateReport(meta);
                    }
                }
                if (serviceError != null) {
                    setFailedStatus(serviceError);
                }
            } catch (CancellationException E) {
                setCancelledStatus();
                LOG.log(Level.INFO, "cancelled.");
            } catch (Exception e) {
                setFailedStatus(e);
                LOG.log(Level.SEVERE, null, e);
            } finally {
                // manage exporation action

                try {
                    final ModelSession ms = Config.getSessionStore().getSession(suid);

                    final String[] inputs = ms.getAttachments();
                    final File[] resf = resultfiles;

                    if (resf != null) {
                        processResults(resf);
                    }

                    if (frep != null) {
                        File[] repf = frep.toArray(new File[0]);
                        processResults(repf);
                    }
                    File ws = getWorkspaceDir();

                    // archive management
                    new Thread(() -> {
                        if (Config.isArchiveEnabled()) {
                            try {
                                // close log before archiving.
                                LOG.close();
                                // copy back the files to the workspace to make them 
                                // a part o fthe archive
                                File req = new File(getResultsDir(), REQUEST_FILE);
                                if (req.exists()) {
                                    FileUtils.copyFileToDirectory(req, getWorkspaceDir());
                                }
                                File res = new File(getResultsDir(), RESPONSE_FILE);
                                if (res.exists()) {
                                    FileUtils.copyFileToDirectory(res, getWorkspaceDir());
                                }
                                File log = new File(getResultsDir(), LOG_FILE);
                                if (log.exists()) {
                                    FileUtils.copyFileToDirectory(log, getWorkspaceDir());
                                }
                                File archive = ZipFiles.zip(getWorkspaceDir());
                                // move it from session store to archive store.
                                // turn session into archive
                                DateFormat df = Dates.newISOFormat(tz);
                                Date now = new Date();
                                Date expDate = Dates.futureDate(now, Dates.getDurationSec("csip.archive.ttl"));
                                ModelArchive ma = new ModelArchive(df.format(now), df.format(expDate), req_url, ms.getStatus(), ms.getReqIP());
                                Config.getArchiveStore().archiveSession(suid, ma, archive);
                                FileUtils.deleteQuietly(archive);
                                LOG.info("Archived : " + suid);
                                Config.getAccessLogStore().log(suid, ms.getService(), ms.getReqIP(), Services.LOCAL_IP_ADDR, ms.getTstamp(), "Archived", -1);
                            } catch (Exception ex) {
                                LOG.log(Level.SEVERE, null, ex);
                            }
                        }
                        if (!Config.getBoolean("csip.keepworkspace", false)) {
                            FileUtils.deleteQuietly(getWorkspaceDir());
                        }
                    }).start();

                    // workspace might be gone from here on forward.
                    // copy/save and cleanup
                    // to this in a thread 
                    new Thread(() -> {
                        Sweeper r = new Sweeper(ws, hasResultsDir() ? getResultsDir() : null, suid);

                        if (ms.getStatus().equals(FAILED)) {
                            keepResults = Dates.getDurationSec("csip.session.ttl.failed");
                        } else if (ms.getStatus().equals(CANCELED)) {
                            keepResults = Dates.getDurationSec("csip.session.ttl.cancelled");
                        }
                        if (keepResults > 0) {
                            Config.getTimer().schedule(r, keepResults * 1000);
                        } else {
                            r.run();
                        }
                    }).start();
                } catch (Exception ex) {
                    LOG.log(Level.SEVERE, null, ex);
                }
                LOG.close();   // this might already being closed if archived.
                // remove the running session.
                Config.getModelTasks().remove(this);
            }
        }


        long getKeepResults() {
            return keepResults;
        }


        void cancel() {
            task.cancel(true);
        }


        ModelDataService getService() {
            return ModelDataService.this;
        }


        @Override
        public String toString() {
            return "\n[" + suid + " - " + status + " - " + start.toString() + "]";
        }


        // status management.
        private void store(JSONObject metainfo, JSONArray results) {
            try {
                JSONObject response = JSONUtils.newResponse(getRequest(), results, metainfo);

                FileUtils.writeStringToFile(new File(getResultsDir(), RESPONSE_FILE),
                        response.toString(2).replace("\\/", "/"));

                String _status = metainfo.getString(KEY_STATUS);
                String tstamp = metainfo.getString(KEY_TSTAMP);
                String exp_date = "";
                if (metainfo.has(KEY_EXPIRATION_DATE)) {
                    exp_date = metainfo.getString(KEY_EXPIRATION_DATE);
                }
                String cputime = "";
                if (metainfo.has(KEY_CPU_TIME)) {
                    cputime = metainfo.getString(KEY_CPU_TIME);
                }
                boolean hasReport = metainfo.has("report") && metainfo.getBoolean("report");

                ModelSession ms = new ModelSession(tstamp, exp_date, getServicePath(), _status,
                        Services.LOCAL_IP_ADDR, cputime, req_remoteIp, inputs, hasReport, progress);
                Config.getSessionStore().setSession(suid, ms);

                // log status change
                int dur = (cputime.isEmpty() ? -1 : Integer.parseInt(cputime));
                Config.getAccessLogStore().log(getSUID(), getServicePath(), req_remoteIp, Services.LOCAL_IP_ADDR, tstamp, _status, dur);
            } catch (Exception ex) {
                LOG.log(Level.SEVERE, null, ex);
            }
        }


        private JSONObject updateMetadata(String status) {
            this.status = status;
            JSONObject metainfo = getMetainfo();
            try {
                metainfo.put(KEY_STATUS, status);
                metainfo.put(KEY_SUUID, suid);
                metainfo.put(KEY_CLOUD_NODE, Services.LOCAL_IP_ADDR);
                metainfo.put(KEY_TSTAMP, Dates.newISOFormat(tz).format(start));
                metainfo.put(KEY_SERVICE_URL, req_url);
                metainfo.put(KEY_REQ_IP, req_remoteIp);

                String reqContext = getRequestContext().substring(1);
                String v = Config.getString(reqContext + ".version");
                if (v != null) {
                    metainfo.put(reqContext + ".version", v);
                }
                metainfo.put("csip.version", Config.getString("csip.version"));
                if (progress != null) {
                    metainfo.put(KEY_PROGRESS, progress);
                }
            } catch (JSONException ex) {
                LOG.log(Level.SEVERE, null, ex);
            }
            return metainfo;
        }


        private void setRunningStatus() {
            try {
                JSONObject metainfo = updateMetadata(RUNNING);
                store(metainfo, null);
            } catch (Exception ex) {
                LOG.log(Level.SEVERE, null, ex);
            }
        }


        private JSONObject setFinishedStatus(JSONArray results) {
            try {
                // only is progress is being touched, set it to 100%
                JSONObject metainfo = updateMetadata(FINISHED);
                metainfo.put(KEY_CPU_TIME, Dates.diffInMillis(start, new Date()));
                DateFormat df = Dates.newISOFormat(tz);
                Date expDate = Dates.futureDate(getKeepResults());
                metainfo.put(KEY_EXPIRATION_DATE, df.format(expDate));
                store(metainfo, results);
                return metainfo;
            } catch (JSONException ex) {
                LOG.log(Level.SEVERE, null, ex);
                return null;
            }
        }


        private void setFailedStatus(Throwable err) {
            String message = JSONUtils.getErrorMessage(err);
            try {
                JSONObject metainfo = updateMetadata(FAILED);
                metainfo.put(KEY_CPU_TIME, Dates.diffInMillis(start, new Date()));
                DateFormat df = Dates.newISOFormat(tz);
                Date expDate = Dates.futureDate(getKeepResults());
                metainfo.put(KEY_EXPIRATION_DATE, df.format(expDate));
                metainfo.put(ERROR, message);
                if (Config.getBoolean("csip.response.stacktrace")) {
                    JSONArray trace = JSONUtils.getJSONStackTrace(err);
                    if (trace != null) {
                        metainfo.put("stacktrace", trace);
                    }
                }
                store(metainfo, null);
            } catch (JSONException ex) {
                LOG.log(Level.SEVERE, null, ex);
            }
        }


        private void setCancelledStatus() {
            try {
                JSONObject metainfo = updateMetadata(CANCELED);
                metainfo.put(KEY_CPU_TIME, Dates.diffInMillis(start, new Date()));
                store(metainfo, null);
            } catch (JSONException ex) {
                LOG.log(Level.SEVERE, null, ex);
            }
        }
    }
}