content: /*
   * Gretty
   *
   * Copyright (C) 2013-2015 Andrey Hihlovskiy and contributors.
   *
   * See the file "LICENSE" for copying and usage permission.
   * See the file "CONTRIBUTORS" for complete list of contributors.
   */
  package org.akhikhl.gretty;
  
  import java.io.File;
  import java.io.IOException;
  import java.net.JarURLConnection;
  import java.net.URISyntaxException;
  import java.net.URL;
  import java.net.URLClassLoader;
  import java.net.URLConnection;
  import java.util.Collections;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.Set;
  import java.util.StringTokenizer;
  import javax.servlet.ServletContext;
  import org.apache.juli.logging.Log;
  import org.apache.juli.logging.LogFactory;
  import org.apache.tomcat.JarScanner;
  import org.apache.tomcat.JarScannerCallback;
  import org.apache.tomcat.util.file.Matcher;
  import org.apache.tomcat.util.res.StringManager;
  import org.apache.tomcat.util.scan.Constants;
  
  /**
   * This is modified version of class
   * org.apache.tomcat.util.scan.StandardJarScanner. Modification compared to
   * original version includes:
   * <ul>
   * <li>skipPatterns parameter, allowing to specify jar skip patterns.</li>
   * </ul>
   *
   * @author akhikhl
   */
  public class JarScannerEx implements JarScanner {
  
    private static final Log log = LogFactory.getLog(JarScannerEx.class);
  
    private static final Set<String> defaultJarsToSkip = new HashSet<String>();
  
    protected final JarSkipPatterns skipPatterns;
  
    JarScannerEx(JarSkipPatterns skipPatterns) {
      this.skipPatterns = skipPatterns;
    }
  
    /**
     * The string resources for this package.
     */
    private static final StringManager sm
            = StringManager.getManager(Constants.Package);
  
    static {
      String jarList = System.getProperty(Constants.SKIP_JARS_PROPERTY);
      if (jarList != null) {
        StringTokenizer tokenizer = new StringTokenizer(jarList, ",");
        while (tokenizer.hasMoreElements()) {
          String token = tokenizer.nextToken().trim();
          if (token.length() > 0) {
            defaultJarsToSkip.add(token);
          }
        }
      }
    }
  
    /**
     * Controls the classpath scanning extension.
     */
    private boolean scanClassPath = true;
  
    public boolean isScanClassPath() {
      return scanClassPath;
    }
  
    public void setScanClassPath(boolean scanClassPath) {
      this.scanClassPath = scanClassPath;
    }
  
    /**
     * Controls the testing all files to see of they are JAR files extension.
     */
    private boolean scanAllFiles = false;
  
    public boolean isScanAllFiles() {
      return scanAllFiles;
    }
  
    public void setScanAllFiles(boolean scanAllFiles) {
      this.scanAllFiles = scanAllFiles;
    }
  
    /**
     * Controls the testing all directories to see of they are exploded JAR files
     * extension.
     */
    private boolean scanAllDirectories = false;
  
    public boolean isScanAllDirectories() {
      return scanAllDirectories;
    }
  
    public void setScanAllDirectories(boolean scanAllDirectories) {
      this.scanAllDirectories = scanAllDirectories;
    }
  
    /**
     * Controls the testing of the bootstrap classpath which consists of the
     * runtime classes provided by the JVM and any installed system extensions.
     */
    private boolean scanBootstrapClassPath = false;
  
    public boolean isScanBootstrapClassPath() {
      return scanBootstrapClassPath;
    }
  
    public void setScanBootstrapClassPath(boolean scanBootstrapClassPath) {
      this.scanBootstrapClassPath = scanBootstrapClassPath;
    }
  
    /**
     * Scan the provided ServletContext and classloader for JAR files. Each JAR
     * file found will be passed to the callback handler to be processed.
     *
     * @param context The ServletContext - used to locate and access WEB-INF/lib
     * @param classloader The classloader - used to access JARs not in WEB-INF/lib
     * @param callback The handler to process any JARs found
     * @param jarsToSkip List of JARs to ignore. If this list is null, a default
     * list will be read from the system property defined by
     * {@link Constants#SKIP_JARS_PROPERTY}
     */
    @Override
    public void scan(ServletContext context, ClassLoader classloader,
            JarScannerCallback callback, Set<String> jarsToSkip) {
  
      if (log.isTraceEnabled()) {
        log.trace(sm.getString("jarScan.webinflibStart"));
      }
  
      jarsToSkip = jarsToSkip == null ? new HashSet() : new HashSet(jarsToSkip);
      jarsToSkip.addAll(skipPatterns.asSet());
      jarsToSkip = Collections.unmodifiableSet(jarsToSkip);
  
      final Set<String> ignoredJars;
      if (jarsToSkip == null) {
        ignoredJars = defaultJarsToSkip;
      } else {
        ignoredJars = jarsToSkip;
      }
  
      // Scan WEB-INF/lib
      Set<String> dirList = context.getResourcePaths(Constants.WEB_INF_LIB);
      if (dirList != null) {
        Iterator<String> it = dirList.iterator();
        while (it.hasNext()) {
          String path = it.next();
          if (path.endsWith(Constants.JAR_EXT)
                  && !Matcher.matchName(ignoredJars,
                          path.substring(path.lastIndexOf("/") + 1))) {
            // Need to scan this JAR
            if (log.isDebugEnabled()) {
              log.debug(sm.getString("jarScan.webinflibJarScan", path));
            }
            URL url = null;
            try {
              // File URLs are always faster to work with so use them
              // if available.
              String realPath = context.getRealPath(path);
              if (realPath == null) {
                url = context.getResource(path);
              } else {
                url = (new File(realPath)).toURI().toURL();
              }
              process(callback, url);
            } catch (IOException e) {
              log.warn(sm.getString("jarScan.webinflibFail", url), e);
            }
          } else {
            if (log.isTraceEnabled()) {
              log.trace(sm.getString("jarScan.webinflibJarNoScan", path));
            }
          }
        }
      }
  
      // Scan the classpath
      if (scanClassPath && classloader != null) {
        if (log.isTraceEnabled()) {
          log.trace(sm.getString("jarScan.classloaderStart"));
        }
  
        ClassLoader loader = classloader;
  
        ClassLoader stopLoader = null;
        if (!scanBootstrapClassPath) {
          // Stop when we reach the bootstrap class loader
          stopLoader = ClassLoader.getSystemClassLoader().getParent();
        }
  
        Set<URL> processedUrls = new HashSet<URL>();
  
        while (loader != null && loader != stopLoader) {
          if (loader instanceof URLClassLoader) {
            URL[] urls = ((URLClassLoader) loader).getURLs();
            for (int i = 0; i < urls.length; i++) {
              URL url = urls[i];
              if (processedUrls.contains(url)) {
                continue;
              }
              processedUrls.add(url);
              // Extract the jarName if there is one to be found
              String jarName = getJarName(url);
  
              // Skip JARs known not to be interesting and JARs
              // in WEB-INF/lib we have already scanned
              if (jarName != null
                      && !(Matcher.matchName(ignoredJars, jarName)
                      || url.toString().contains(Constants.WEB_INF_LIB + jarName))) {
                if (log.isDebugEnabled()) {
                  log.debug(sm.getString("jarScan.classloaderJarScan", url));
                }
                try {
                  process(callback, url);
                } catch (IOException ioe) {
                  log.warn(sm.getString(
                          "jarScan.classloaderFail", url), ioe);
                }
              } else {
                if (log.isTraceEnabled()) {
                  log.trace(sm.getString("jarScan.classloaderJarNoScan", url));
                }
              }
            }
          }
          loader = loader.getParent();
        }
  
      }
    }
  
    /*
     * Scan a URL for JARs with the optional extensions to look at all files
     * and all directories.
     */
    private void process(JarScannerCallback callback, URL url)
            throws IOException {
  
      if (log.isTraceEnabled()) {
        log.trace(sm.getString("jarScan.jarUrlStart", url));
      }
  
      URLConnection conn = url.openConnection();
      if (conn instanceof JarURLConnection) {
        callback.scan((JarURLConnection) conn);
      } else {
        String urlStr = url.toString();
        if (urlStr.startsWith("file:") || urlStr.startsWith("jndi:")
                || urlStr.startsWith("http:") || urlStr.startsWith("https:")) {
          if (urlStr.endsWith(Constants.JAR_EXT)) {
            URL jarURL = new URL("jar:" + urlStr + "!/");
            callback.scan((JarURLConnection) jarURL.openConnection());
          } else {
            File f;
            try {
              f = new File(url.toURI());
              if (f.isFile() && scanAllFiles) {
                // Treat this file as a JAR
                URL jarURL = new URL("jar:" + urlStr + "!/");
                callback.scan((JarURLConnection) jarURL.openConnection());
              } else if (f.isDirectory() && scanAllDirectories) {
                File metainf = new File(f, "META-INF");
                if (metainf.isDirectory()) {
                  callback.scan(f);
                }
              }
            } catch (URISyntaxException e) {
              // Wrap the exception and re-throw
              IOException ioe = new IOException();
              ioe.initCause(e);
              throw ioe;
            }
          }
        }
      }
  
    }
  
    /*
     * Extract the JAR name, if present, from a URL
     */
    private String getJarName(URL url) {
  
      String name = null;
  
      String path = url.getPath();
      int end = path.indexOf(Constants.JAR_EXT);
      if (end != -1) {
        int start = path.lastIndexOf("/", end);
        name = path.substring(start + 1, end + 4);
      } else if (isScanAllDirectories()) {
        int start = path.lastIndexOf("/");
        name = path.substring(start + 1);
      }
  
      return name;
    }
  
  }
  