Binaries.java [src/csip/utils] Revision:   Date:
/*
 * $Id: 2.7+52 Binaries.java b5d09eb32576 2023-08-16 od $
 *
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * a Model-as-a-Service framework, API and application suite.
 *
 * 2012-2022, Olaf David and others, OMSLab, Colorado State University.
 *
 * OMSLab licenses this file to you under the MIT license.
 * See the LICENSE file in the project root for more information.
 */
package csip.utils;

import csip.Config;
import static csip.Config.CSIP_DIR;
import static csip.Config.CSIP_JDBC_CHECKVALID;
import csip.api.server.Executable;
import csip.api.server.ServiceException;
import csip.SessionLogger;
import csip.Utils;
import csip.annotations.Resource;
import csip.annotations.ResourceType;
import static csip.annotations.ResourceType.CLASSNAME;
import static csip.annotations.ResourceType.JAR;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.sql.DataSource;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.apache.tomcat.jdbc.pool.DataSourceProxy;
import org.apache.tomcat.jdbc.pool.PoolProperties;

/**
 *
 * @author od
 */
public class Binaries {

  public static final String STDOUT = "-stdout.txt";
  public static final String STDERR = "-stderr.txt";

  public static final String AUTO_RUN = "auto";

  public static final String DRIVER = "driverClassName";

  /**
   * JDBC Pool class.
   */
  private static class JDBCPool {

    DataSource datasource;
    String url;
    String resolved_url = "";
    Map<String, String> env;

    /**
     * Create a pool
     *
     * @param url
     * @param env
     */
    JDBCPool(String url, Map<String, String> env) {
      this.url = url;
      this.env = env;
    }

    /**
     * Get the pool connection.
     *
     * @param logger
     * @return
     * @throws ServiceException
     */
    Connection getConnection(SessionLogger logger) throws ServiceException {
      try {
        // probe for configuration change.
        String res_url = Utils.resolve(url);
        if (datasource == null || !res_url.equals(resolved_url)) {
          //Close previous datasource if it exists
          if (datasource != null)
            shutdown();

          //Remember this connection string in case it has changed.
          resolved_url = res_url;
          PoolProperties p = new PoolProperties();
          defaultProperties(p);
          p.setUrl(resolved_url);
          logger.info(" JDBC pool property:  url:" + resolved_url);

          // load the driver if not already done do
          if (!env.containsKey(DRIVER)) {
            String rurl = resolved_url.toLowerCase();
            if (rurl.contains(":postgresql:"))
              env.put(DRIVER, "org.postgresql.Driver");
            else if (rurl.contains(":sqlserver:"))
              env.put(DRIVER, "com.microsoft.sqlserver.jdbc.SQLServerDriver");
            else if (rurl.contains(":mysql:"))
              env.put(DRIVER, "com.mysql.jdbc.Driver");
            else if (rurl.contains(":sqlite:"))
              env.put(DRIVER, "org.sqlite.JDBC");
            else if (rurl.contains(":oracle:"))
              env.put(DRIVER, "oracle.jdbc.driver.OracleDriver");
            else if (rurl.contains(":db2:"))
              env.put(DRIVER, "com.ibm.db2.jcc.DB2Driver");
            else if (rurl.contains(":sdm:"))
              env.put(DRIVER, "csip.sdm.SDMDriver");

          }

          // overwrite the defaults.
          Properties dbp = new Properties();
          for (String key : env.keySet()) {
            String val = env.get(key);
            try {
              BeanUtils.setProperty(p, key, val);
              logger.info("Set JDBC pool property: " + key + ": " + val);
            } catch (IllegalAccessException | InvocationTargetException E) {
              dbp.setProperty(key, val);
              logger.info("Set DB property: " + key + ": " + val);
            }
          }
          if (!dbp.isEmpty())
            p.setDbProperties(dbp);

          logger.info("JDBC Pool properties:" + p.toString());
          datasource = new org.apache.tomcat.jdbc.pool.DataSource(p);
          logger.info("Created JDBC datasource: " + datasource.toString());
        }
        return datasource.getConnection();
      } catch (SQLException ex) {
        logger.log(Level.SEVERE, null, ex);
        throw new ServiceException("Unable to connect..  Please check the configuration parameter", ex);
      }
    }

    /**
     * set the default properties.
     *
     * @param p
     */
    private void defaultProperties(PoolProperties p) {
      p.setDefaultAutoCommit(false);
      p.setJmxEnabled(false);
      p.setTestOnBorrow(true);
      p.setValidationQuery("SELECT 1");
      p.setTestOnReturn(false);
      p.setValidationInterval(30000);
      p.setMaxWait(10000);
      p.setRemoveAbandonedTimeout(60);
      p.setRemoveAbandoned(true);
      p.setInitialSize(10);
      p.setMaxActive(250);
      p.setMaxIdle(100);
      p.setMinIdle(10);
      p.setSuspectTimeout(60);
      p.setTimeBetweenEvictionRunsMillis(30000);
      p.setMinEvictableIdleTimeMillis(60000);
    }

    /**
     * Shutdown this pool.
     */
    void shutdown() {
      if (datasource instanceof DataSourceProxy) {
        ((DataSourceProxy) datasource).close(true);
        System.out.println("Closed datasource " + datasource.toString());
      }
      datasource = null;
    }
  }

  /**
   * Map of JDBCPools.
   */
  private static final Map<String, JDBCPool> jdbcPools = new HashMap<>();
  private static final List<Resource> NO_RES = new ArrayList<>();

  private static final Object resource_lock = new Object();

  private static final long DATE_AT_STARTUP = new Date().getTime();
  private static final long DATE_AT_STARTUP_SEC = DATE_AT_STARTUP / 1000l;

  private static SimpleCache<Class<?>, List<Resource>> rc = new SimpleCache<>();
  private static SimpleCache<Class<?>, Boolean> extractCache = new SimpleCache<>();

  static boolean newCP = Config.getBoolean("csip.newcp", false);

  /**
   * Shuts down all JDBC pools.
   */
  public static void shutdownJDBC() {
    if (newCP) {
      ConnectionPools.singleton().shutdownJDBC();
      return;
    }
    synchronized (jdbcPools) {
      for (JDBCPool pool : jdbcPools.values()) {
        pool.shutdown();
      }
      jdbcPools.clear();
    }
  }

  /**
   * Get the architecture
   *
   * @return the architecture
   */
  public static String getArch() {
    String os = System.getProperty("os.name").toLowerCase();
    if (os.contains("bsd"))
      os = "bsd";
    else
      os = os.substring(0, 3);
    return os + "-" + System.getProperty("os.arch").toLowerCase();
  }

  /**
   *
   * @param p1
   * @param workspace
   * @param LOG
   * @return
   */
  private static Executable asExecutable(final Execution p1, final File workspace, final SessionLogger LOG) {

    return new Executable() {
      File stdout = new File(workspace, p1.getFile().getName() + "-" + p1.hashCode() + STDOUT);
      File stderr = new File(workspace, p1.getFile().getName() + "-" + p1.hashCode() + STDERR);

      Writer out;
      Writer err;

      @Override
      public File stdout() {
        return stdout;
      }

      @Override
      public File stderr() {
        return stderr;
      }

      @Override
      public String getName() {
        return p1.getFile().toString();
      }

      @Override
      public void setArguments(Object... args) {
        List<Object> l = new ArrayList<>();
        Object[] o = p1.getArguments();
        if (o.length > 0 && o[0] instanceof String && p1.getFile().getName().contains("wine"))
          // keep the first argument, if execution is wine.
          l.add(o[0]);
        l.addAll(Arrays.asList(args));
        p1.setArguments(l);
      }

      @Override
      public void setOptions(Object... opts) {
        if (opts == null)
          return;
        p1.setOptions(opts);
      }

      @Override
      public void addArguments(Object... args) {
        if (args == null)
          return;
        List<Object> l = new ArrayList<>();
        l.addAll(Arrays.asList(p1.getArguments()));
        l.addAll(Arrays.asList(args));
        p1.setArguments(l);
      }

      @Override
      public void addOptions(Object... opts) {
        if (opts == null)
          return;
        List<Object> l = new ArrayList<>();
        l.addAll(Arrays.asList(p1.getOptions()));
        l.addAll(Arrays.asList(opts));
        p1.setOptions(l);
      }

      @Override
      public Object[] getArguments() {
        return p1.getArguments();
      }

      @Override
      public Object[] getOptions() {
        return p1.getOptions();
      }

      @Override
      public Map<String, String> environment() {
        return p1.environment();
      }

      @Override
      public int exec() throws IOException {
        p1.redirectOutput(out != null ? out : new FileWriter(stdout()));
        p1.redirectError(err != null ? err : new FileWriter(stderr()));
        p1.setWorkingDirectory(workspace);
        p1.setLogger(LOG);

        int ret = p1.exec();
        if (ret != 0 && LOG.isLoggable(Level.SEVERE) && stderr().exists()) {
          String s = FileUtils.readFileToString(stderr(), "UTF-8");
          LOG.severe(s);
        }
        return ret;
      }

      @Override
      public void redirectDefaults() throws IOException {
        out = new FileWriter(stdout(), true);
        err = new FileWriter(stderr(), true);
      }

      @Override
      public void redirectOutput(String file) throws IOException {
        if (file == null)
          throw new IOException("missing file name for redirect.");
        out = new FileWriter(new File(workspace, file));
      }

      @Override
      public void redirectError(String file) throws IOException {
        if (file == null)
          throw new IOException("missing file name for redirect.");
        err = new FileWriter(new File(workspace, file));
      }

      @Override
      public void redirectOutput(StringWriter w) throws IOException {
        out = w;
      }

      @Override
      public void redirectError(StringWriter w) throws IOException {
        err = w;
      }

      @Override
      public void setStdoutHandler(Executable.StdHandler handler) {
        p1.setStdoutHandler(handler);
      }

      @Override
      public void setStderrHandler(Executable.StdHandler handler) {
        p1.setStderrHandler(handler);
      }

      @Override
      public void setTimeout(long time, TimeUnit unit) {
        p1.setTimeout(time, unit);
      }

    };
  }

  /**
   *
   * @param c
   * @param id
   * @return
   * @throws ServiceException
   */
  public static Resource getResource(Class<?> c, String id) throws ServiceException {
    return getResourceById(c, id);
  }

  /**
   * Get a file resources.
   *
   * @param c the service class
   * @param workspace The workspace (for name resolution)
   * @param id the Resource ID
   * @return the file.
   * @throws csip.api.server.ServiceException
   */
  public static File getResourceFile(Class<?> c, File workspace, String id) throws ServiceException {
    Resource resource = getResourceById(c, id);
    if (resource == null)
      throw new ServiceException("No such resource: '" + id + "'");

    String file = resource.file();
    if (file == null || file.isEmpty())
      return null;

    file = Utils.resolve(file, workspace);
    if (resource.type() == ResourceType.REFERENCE) {
      File f = new File(file);
      if (!f.exists() || !f.canRead())
        throw new ServiceException("Cannot find file: '" + f + "'");

      return f;
    }

    File csip_home = new File(Config.getString(CSIP_DIR));
    if (file.endsWith("zip") && resource.type() == ResourceType.ARCHIVE)
      return new File(csip_home, file.substring(0, file.indexOf('.')));

    File f = new File(csip_home, file);
    if (!f.exists() || !f.canRead())
      throw new ServiceException("Cannot find file: '" + f + "'");

    return f;
  }

  /**
   * Get the resource as java file.
   *
   * @param c the service class
   * @param id the resource id
   * @param workspace the session workspace
   * @param jars the jar files
   * @param LOG Session Logger
   * @return the Executable
   * @throws ServiceException
   */
  public static Executable getResourceJava(Class<?> c, String id, final File workspace,
      List<File> jars, SessionLogger LOG) throws ServiceException {
    Resource r = getResourceById(c, id);
    if (r == null || r.type() != ResourceType.CLASSNAME)
      throw new ServiceException("Not found: " + id);

    return getResourceJava(r, workspace, jars, LOG);
  }

  public static Executable getResourceJava(Resource r, final File workspace,
      List<File> jars, SessionLogger LOG) throws ServiceException {
    String oms_java_home = Config.getString("java.home", "/opt/jdk1.7.0_45");
    Execution p = new Execution(new File(oms_java_home, "/bin/java"));
    p.setArguments(//r[0].args().split("\\s+"), // no jvmoptions
        "-cp", asClassPath(jars),
        r.file(),
        r.args().split("\\s+")
    );
    if (r.env().length > 0)
      p.environment().putAll(parseEnv(r.env()));

    return asExecutable(p, workspace, LOG);
  }

  public static Executable getResourceJava0(Resource r, File workspace,
      SessionLogger LOG) throws ServiceException {
    if (r.type() != CLASSNAME && r.type() != JAR)
      throw new IllegalArgumentException("Invalid java resource type");

    String oms_java_home = Config.getString("java.home");
    Execution p = new Execution(new File(oms_java_home, "/bin/java"));
    p.setArguments(
        r.type() == JAR ? "-jar" : "",
        Utils.resolve(r.file(), workspace),
        r.args().split("\\s+")
    );
    if (r.env().length > 0)
      p.environment().putAll(parseEnv(r.env()));
    return asExecutable(p, workspace, LOG);
  }

  static String adjustPathSeparator(String s) {
    return s.replace(';', File.pathSeparatorChar).replace(':', File.pathSeparatorChar);
  }

  private static Executable getScriptResource(Resource r,
      SessionLogger LOG, Class<?> service, File workspace,
      String interpreter, String ext, ResourceType t) throws ServiceException {

    Execution p = new Execution(new File(interpreter));

    String file = r.file();
    if (file == null || file.isEmpty()) {
      if (service == null)
        return null;

      file = '/' + service.getName().replace('.', '/') + ext;
    }
    file = Utils.resolve(file, workspace);
    File f = new File(Config.getString(CSIP_DIR), file);
    File csip_home = new File(Config.getString(CSIP_DIR));
    try {
      unpackResource0(file, t, csip_home, LOG);
    } catch (IOException E) {
      throw new ServiceException(E);
    }
    // try again....
    if (!f.exists() || !f.canRead())
      throw new ServiceException("Cannot find file: '" + f + "'");

    p.setArguments(f.toString(), r.args().split("\\s+"));
    if (r.env().length > 0)
      p.environment().putAll(parseEnv(r.env()));

    return asExecutable(p, workspace, LOG);
  }

  public static Executable getResourcePython(Resource r, final File workspace,
      SessionLogger LOG, Class<?> service) throws ServiceException {
    return getScriptResource(r, LOG, service, workspace,
        r.type() == ResourceType.PYTHON3
        ? Config.getString("csip.python3.path", "python3")
        : Config.getString("csip.python.path", "python"), ".py", ResourceType.PYTHON3);
  }

  public static Executable getResourceRScript(Resource r, final File workspace,
      SessionLogger LOG, Class<?> service) throws ServiceException {
    return getScriptResource(r, LOG, service, workspace,
        Config.getString("csip.rscript.path", "Rscript"), ".r", ResourceType.RSCRIPT);
  }

  public static Executable getResourceBash(Resource r, final File workspace,
      SessionLogger LOG, Class<?> service) throws ServiceException {
    return getScriptResource(r, LOG, service, workspace,
        Config.getString("csip.bash.path", "bash"), ".sh", ResourceType.RSCRIPT);
  }

  /**
   *
   * @param r
   * @param options
   * @param workspace
   * @param jars
   * @param loglevel
   * @param LOG
   * @return
   * @throws ServiceException
   */
  public static Executable getResourceOMSDSL(File r, Object[] options,
      final File workspace, List<File> jars, String loglevel, SessionLogger LOG) throws ServiceException {
    String oms_java_home = Config.getString("oms.java.home", "/opt/jdk1.7.0_45");
    Execution p = new Execution(new File(oms_java_home, "/bin/java"));
    p.setArguments(options, // jvmoptions
        "-cp", asClassPath(jars),
        "oms3.CLI",
        "-l", loglevel,
        "-r", r.toString()
    );
    return asExecutable(p, workspace, LOG);
  }

  /**
   * Get the JDBC connection from a resource definition.
   *
   * @param c
   * @param id
   * @param LOG
   * @return
   * @throws ServiceException
   */
  public static Connection getResourceJDBC(Class<?> c, String id, SessionLogger LOG) throws ServiceException {
    if (newCP)
      return ConnectionPools.singleton().getResourceJDBC(c, id, LOG);

    synchronized (jdbcPools) {
      JDBCPool p = jdbcPools.get(id);
      if (p == null) {
        Resource r = getResourceById(c, id);
        if (r != null) {
          if (r.type() == ResourceType.JDBC) {
            String url = r.file();
            if (url != null && !url.isEmpty()) {
              Map<String, String> env = parseEnv(r.env());
              p = new JDBCPool(url, env);
              Connection con = p.getConnection(LOG);
              try {
                int valid = Config.getInt(CSIP_JDBC_CHECKVALID, -1); // default: no checking
                if (valid < -1)
                  valid = -1;

                if (valid == -1 || con.isValid(valid)) {
                  jdbcPools.put(id, p);
                  return con;
                } else {
                  throw new ServiceException("Cannot connect, invalid connection to: " + url);
                }
              } catch (SQLException ex) {
                throw new ServiceException("Invalid timeout: " + url);
              }
            } else {
              throw new ServiceException("No url connection string in 'file': " + id);
            }
          } else {
            throw new ServiceException("Not a JDBC resource: " + id);
          }
        } else {
          throw new ServiceException("No such resource: " + id);
        }
      }
      return p.getConnection(LOG);
    }
  }

  /**
   * Add to JDBC pool
   *
   * @param id
   * @param url
   */
  public static void addToJDBCPool(String id, String url) {
    if (newCP)
      ConnectionPools.singleton().addToJDBCPool(id, url);
    else
      jdbcPools.put(id, new JDBCPool(url, new HashMap<>()));
  }

  /**
   * Add to JDBC pool
   *
   * @param id
   * @param url
   * @param env
   */
  public static void addToJDBCPool(String id, String url, Map<String, String> env) {
    if (newCP)
      ConnectionPools.singleton().addToJDBCPool(id, url, env);
    else
      jdbcPools.put(id, new JDBCPool(url, env));
  }

  /**
   * Get Connection.
   *
   * @param id
   * @param logger
   * @return
   * @throws ServiceException
   */
  public static Connection getConnection(String id, SessionLogger logger) throws ServiceException {
    if (newCP)
      return ConnectionPools.singleton().getConnection(id, logger);

    JDBCPool p = jdbcPools.get(id);
    if (p == null)
      throw new ServiceException("No such resource: " + id);

    return p.getConnection(logger);
  }

  public static void doInstall(Resource resource, SessionLogger LOG) throws IOException {
    String file = resource.file();
    if (file.isEmpty())
      return;

    file = Utils.resolve(file);
    File csip_home = new File(Config.getString(CSIP_DIR));
    File outFile = new File(csip_home, file);

    synchronized (resource_lock) {
      // check by date, ignore milliseconds since they might be not stored.
      if (outFile.exists() && (DATE_AT_STARTUP_SEC == (outFile.lastModified() / 1000l)))
        return;

      File executable = unpackResource0(resource, csip_home, LOG);
      if (!executable.canExecute())
        executable.setExecutable(true, true);

      Execution p = null;
      List<Object> args = new ArrayList<>();
      if (resource.wine() && File.pathSeparatorChar == ':') {
        p = new Execution(new File(Config.getString("wine.path", "/usr/bin/wine")));
        args.add(executable.toString());
      } else {
        p = new Execution(new File(executable.toString()));
      }
      if (!resource.args().isEmpty())
        args.add(resource.args().split("\\s+"));

      if (args.size() > 0)
        p.setArguments(args);

      if (resource.env().length > 0)
        p.environment().putAll(parseEnv(resource.env()));

      LOG.info("executing install : " + outFile);
      Executable ex = asExecutable(p, outFile.getParentFile(), LOG);
      int ret = ex.exec();

      if (LOG.isLoggable(Level.INFO)) {
        String std = (ret == 0) ? Binaries.STDOUT : Binaries.STDERR;
        FilenameFilter ff = new WildcardFileFilter("*" + std, IOCase.INSENSITIVE);
        File[] f = outFile.getParentFile().listFiles(ff);
        if (f != null && f.length > 0)
          LOG.info(f[0] + ":\n" + FileUtils.readFileToString(f[0], "UTF-8"));
      }
    }
  }

  /**
   * Get a file resources.
   *
   * @param id
   * @param workspace
   * @param LOG
   * @param service
   * @return
   * @throws csip.api.server.ServiceException
   */
  public static Executable getResourceExe0(Class<?> service, String id,
      final File workspace, SessionLogger LOG) throws ServiceException {
    Resource resource = getResourceById(service, id);
    if (resource == null)
      throw new ServiceException("Not found: " + id);
    return getResourceExe0(resource, workspace, LOG, service);
  }

  public static Executable getResourceExe0(Resource resource,
      final File workspace, SessionLogger LOG, Class<?> service) throws ServiceException {
    File executable = null;
    String file = resource.file();
    switch (resource.type()) {
      case REFERENCE:
        if (resource.file() == null || resource.file().isEmpty())
          return null;
        file = Utils.resolve(file, workspace);
        executable = new File(file);

        // check only if there is a reference to an absolute file.
        // relative commads could use the PATH.
        if (executable.isAbsolute() && !executable.exists())
          throw new ServiceException("Referenced executable not found: " + file);

        break;
      case EXECUTABLE:
        try {
          File csip_home = new File(Config.getString(CSIP_DIR));
          if (file == null || file.isEmpty()) {
            if (service == null)
              return null;

            file = '/' + service.getName().replace('.', '/') + ".exe";
            File f = new File(csip_home, file);
            try {
              executable = unpackResource0(file, ResourceType.EXECUTABLE, csip_home, LOG);
            } catch (IOException E) {
              throw new ServiceException(E);
            }
            // try again....
            if (!f.exists() || !f.canRead())
              throw new ServiceException("Cannot find python file: '" + f + "'");

          } else {
            file = Utils.resolve(file, workspace);
            executable = unpackResource0(file, ResourceType.EXECUTABLE, csip_home, LOG);
          }
        } catch (IOException ex) {
          throw new ServiceException(ex);
        }
        break;
      default:
        throw new ServiceException("Illegal resource type: " + resource.type());
    }
    if (executable == null)
      throw new ServiceException("executable is null:for " + file);
    if (!executable.canExecute())
      executable.setExecutable(true, true);

    Execution p = null;
    List<Object> args = new ArrayList<>();
    if (resource.wine() && File.pathSeparatorChar == ':') {
      p = new Execution(new File(Config.getString("wine.path", "/usr/bin/wine")));
      args.add(executable.toString());
    } else {
      p = new Execution(new File(executable.toString()));
    }
    if (!resource.args().isEmpty())
      args.add(resource.args().split("\\s+"));
    if (args.size() > 0)
      p.setArguments(args);
    if (resource.env().length > 0)
      p.environment().putAll(parseEnv(resource.env()));
    return asExecutable(p, workspace, LOG);
  }

  /**
   * Get merged Resources from cache.
   *
   * @param c
   * @return
   */
  public static List<Resource> getMergedResources(Class<?> c) {
    return rc.get(c, Binaries::getMergedResources0);
  }

  /**
   * Get merged Resources
   *
   * @param c
   * @return
   */
  private static List<Resource> getMergedResources0(Class<?> c) {
    Resource[] r = c.getAnnotationsByType(Resource.class);
    if (r == null || r.length == 0)
      return NO_RES;
    List<Resource> l = new ArrayList<>();
    for (Resource res : r) {
      if (res.from() != Object.class)
        l.addAll(getMergedResources0(res.from()));
      else
        l.add(res);
    }
    return l;
  }

  /**
   * Get Resource by type.
   *
   * @param c
   * @param type
   * @return
   */
  public static List<Resource> getResourcesByType(Class<?> c, ResourceType type) {
    List<Resource> mr = getMergedResources(c);
    if (mr == NO_RES)
      return mr;
    List<Resource> l = new ArrayList<>();
    for (Resource resource : mr) {
      if (resource.type() == type)
        l.add(resource);
    }
    return l;
  }

  /**
   * Get the resource by ID.
   *
   * @param c
   * @param id
   * @return
   */
  public static Resource getResourceById(Class<?> c, String id) {
    for (Resource resource : getMergedResources(c)) {
      if (id.equals(resource.id()))
        return resource;
    }
    return null;
  }

  /**
   * Get the auto execution resource.
   *
   * @param c Class e
   * @return
   */
  public static Resource getAutoExecResource(Class<?> c) {
    for (Resource resource : getMergedResources(c)) {
      if ((resource.id().equals("") || resource.id().equals(AUTO_RUN))
          && ((resource.type() == ResourceType.PYTHON3)
          || resource.type() == ResourceType.PYTHON2
          || resource.type() == ResourceType.EXECUTABLE
          || resource.type() == ResourceType.CLASSNAME //          || resource.type() == ResourceType.OMS_COMP
          //          || resource.type() == ResourceType.OMS_DSL)
          ))

        return resource;
    }
    return null;
  }

  public static void extractAllResources(Class<?> c, SessionLogger LOG) throws ServiceException {
    // do this only once per class, hence the SimpleCache
    extractCache.get(c, (Class<?> k) -> {
      File csip_home = new File(Config.getString(CSIP_DIR));
      csip_home.mkdirs();
      for (Resource resource : getMergedResources(c)) {
        if (resource.type() == ResourceType.REFERENCE
            || resource.type() == ResourceType.JDBC
            || resource.type() == ResourceType.OUTPUT
            || resource.type() == ResourceType.INSTALL
            || resource.file().isEmpty())
          continue;
        try {
          unpackResource0(resource, csip_home, LOG);
        } catch (IOException ex) {
          LOG.log(Level.SEVERE, null, ex);
        }
      }
      return true;
    });
  }

  public static File unpackResource0(String name, ResourceType resType,
      File file_or_folder, SessionLogger LOG) throws IOException {

    name = Utils.resolve(name.trim());
    LOG.info("accessing resource : " + name);

    File outFile = file_or_folder;
    if (file_or_folder.isDirectory())
      outFile = new File(file_or_folder, name);

    synchronized (resource_lock) {
      // check by date, ignore milliseconds since they might be not stored.
      if (outFile.exists() && (DATE_AT_STARTUP_SEC == (outFile.lastModified() / 1000l)))
        return outFile;

      // extract the whole folder.and anything in it.
      if (name.endsWith("/**") && (resType == ResourceType.ARCHIVE)) {
        name = name.substring(0, name.length() - 3);
        URL url = Binaries.class.getResource(name);
        File file = null;
        try {
          file = new File(url.toURI());
        } catch (URISyntaxException e) {
          file = new File(url.getPath());
        }

        if (!(file.exists() && file.isDirectory()))
          throw new IllegalArgumentException("No such directory: " + name);

        outFile = new File(file_or_folder, name);
        FileUtils.copyToDirectory(file, outFile.getParentFile());
        return outFile;
      }

      if (!outFile.getParentFile().exists())
        outFile.getParentFile().mkdirs();

      URL u = Binaries.class.getResource(name);
      if (u == null)
        throw new IllegalArgumentException("No such resource: " + name);

      InputStream in = new BufferedInputStream(u.openStream());
      FileUtils.copyInputStreamToFile(in, outFile);
      in.close();

      if (resType == ResourceType.EXECUTABLE) {
        if (!outFile.canExecute())
          outFile.setExecutable(true, true);
      }
      outFile.setReadable(true);
      outFile.setLastModified(DATE_AT_STARTUP);
      LOG.info("extracted: " + name + " to " + outFile);

      // file was an archive, unzip it.
      if (name.endsWith(".zip") && (resType == ResourceType.ARCHIVE)) {
        ZipFiles.unzip(outFile);
        LOG.info("unzipped: " + outFile);
      }
      if (name.endsWith(".gz") && (resType == ResourceType.ARCHIVE)) {
        ZipFiles.gunzip(outFile);
        LOG.info("gunzipped: " + outFile);
      }
      return outFile;
    }
  }

  /**
   * This is needed because of services calling the above method.
   *
   * @param r resource
   * @param LOG SessionLogger
   * @param file_or_folder input directory/file
   * @return unpacked resource
   * @throws IOException required by unpackResource0
   */
  public static File unpackResource0(Resource r, File file_or_folder, SessionLogger LOG) throws IOException {
    return unpackResource0(r.file(), r.type(), file_or_folder, LOG);
  }

  /**
   * get the FS usage of that directory/file location in percent.
   *
   * @param file input directory/file
   * @return value 0.0 .. 1.0, 1.0 means 100% full.
   */
  public static double getFSUsage(File file) {
    return 1 - (double) file.getUsableSpace() / file.getTotalSpace();
  }

  /**
   * get the library jars
   *
   * @param dir dir where to look for jar libraries
   * @return the list of jars
   */
  public static List<File> getJars(File dir) {
    File[] f = dir.listFiles((FilenameFilter) new WildcardFileFilter("*.jar"));
    if (f == null)
      return new ArrayList<>();

    return Arrays.asList(f);
  }

  /**
   * Convert the files to a classpath
   *
   * @param f list of files
   * @return classpath of jar files
   */
  public static String asClassPath(List<File> f) {
    StringBuilder b = new StringBuilder();
    for (int i = 0; i < f.size(); i++) {
      if (f.get(i).getName().endsWith(".jar")) {
        b.append(f.get(i).toString());
        if (i < f.size() - 1)
          b.append(File.pathSeparatorChar);

      }
    }
    return b.toString();
  }

  /**
   * Converts a map to system properties.
   *
   * @param m input map
   * @return system properties
   */
  public static String[] asSysProps(Map<String, String> m) {
    List<String> b = new ArrayList<>();
    for (String key : m.keySet()) {
      String val = m.get(key);
      b.add("-D" + key + "=" + val);
    }
    return b.toArray(new String[b.size()]);
  }

  /**
   * Parse environmental variables to be used for execution
   *
   * @param env environmental variables
   * @return map of variable name and value
   */
  public static Map<String, String> parseEnv(String[] env) {
    Map<String, String> m = new HashMap<>();
    if (env == null)
      return m;

    for (String s : env) {
      if (s == null || s.isEmpty())
        continue;

      String t[] = s.trim().split("\\s*=\\s*");
      if (t.length == 2)
        m.put(t[0], Utils.resolve(t[1]));

    }
    return m;
  }

  /**
   * Parses a string into a number of bytes. e.g. "1KB" = 1024,
   *
   * @param in input string
   * @return the size in bytes.
   */
  @SuppressWarnings("fallthrough")
  public static long parseByteSize(String in) {
    in = in.trim().replaceAll(",", ".");
    try {
      return Long.parseLong(in);
    } catch (NumberFormatException e) {
    }
    Matcher m = Pattern.compile("([\\d.,]+)\\s*(\\w)").matcher(in);
    m.find();
    double scale = 1;
    switch (m.group(2).charAt(0)) {
      case 'G':
      case 'g':
        scale *= 1024;
      case 'M':
      case 'm':
        scale *= 1024;
      case 'K':
      case 'k':
        scale *= 1024;
        break;
      default:
        throw new IllegalArgumentException(in);
    }
    return Math.round(Double.parseDouble(m.group(1)) * scale);
  }
}