001/**
002 * Copyright 2010-2016 Boxfuse GmbH
003 * <p/>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p/>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p/>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.avaje.classpath.scanner.internal.scanner.classpath;
017
018import java.io.IOException;
019import java.net.JarURLConnection;
020import java.net.URISyntaxException;
021import java.net.URL;
022import java.net.URLConnection;
023import java.util.Enumeration;
024import java.util.Set;
025import java.util.TreeSet;
026import java.util.jar.JarEntry;
027import java.util.jar.JarFile;
028
029/**
030 * ClassPathLocationScanner for jar files.
031 */
032public class JarFileClassPathLocationScanner implements ClassPathLocationScanner {
033  public Set<String> findResourceNames(String location, URL locationUrl) throws IOException {
034    JarFile jarFile = getJarFromUrl(locationUrl);
035
036    try {
037      // For Tomcat and non-expanded WARs.
038      String prefix = jarFile.getName().toLowerCase().endsWith(".war") ? "WEB-INF/classes/" : "";
039      return findResourceNamesFromJarFile(jarFile, prefix, location);
040    } finally {
041      jarFile.close();
042    }
043  }
044
045  /**
046   * Retrieves the Jar file represented by this URL.
047   *
048   * @param locationUrl The URL of the jar.
049   * @return The jar file.
050   * @throws IOException when the jar could not be resolved.
051   */
052  private JarFile getJarFromUrl(URL locationUrl) throws IOException {
053    URLConnection con = locationUrl.openConnection();
054    if (con instanceof JarURLConnection) {
055      // Should usually be the case for traditional JAR files.
056      JarURLConnection jarCon = (JarURLConnection) con;
057      jarCon.setUseCaches(false);
058      return jarCon.getJarFile();
059    }
060
061    // No JarURLConnection -> need to resort to URL file parsing.
062    // We'll assume URLs of the format "jar:path!/entry", with the protocol
063    // being arbitrary as long as following the entry format.
064    // We'll also handle paths with and without leading "file:" prefix.
065    String urlFile = locationUrl.getFile();
066
067    int separatorIndex = urlFile.indexOf("!/");
068    if (separatorIndex != -1) {
069      String jarFileUrl = urlFile.substring(0, separatorIndex);
070      if (jarFileUrl.startsWith("file:")) {
071        try {
072          return new JarFile(new URL(jarFileUrl).toURI().getSchemeSpecificPart());
073        } catch (URISyntaxException ex) {
074          // Fallback for URLs that are not valid URIs (should hardly ever happen).
075          return new JarFile(jarFileUrl.substring("file:".length()));
076        }
077      }
078      return new JarFile(jarFileUrl);
079    }
080
081    return new JarFile(urlFile);
082  }
083
084  /**
085   * Finds all the resource names contained in this directory within this jar file.
086   *
087   * @param jarFile  The jar file.
088   * @param prefix   The prefix to ignore within the jar file.
089   * @param location The location to look under.
090   * @return The resource names.
091   * @throws java.io.IOException when reading the jar file failed.
092   */
093  private Set<String> findResourceNamesFromJarFile(JarFile jarFile, String prefix, String location) throws IOException {
094    String toScan = prefix + location + (location.endsWith("/") ? "" : "/");
095    Set<String> resourceNames = new TreeSet<String>();
096
097    Enumeration<JarEntry> entries = jarFile.entries();
098    while (entries.hasMoreElements()) {
099      String entryName = entries.nextElement().getName();
100      if (entryName.startsWith(toScan)) {
101        resourceNames.add(entryName.substring(prefix.length()));
102      }
103    }
104
105    return resourceNames;
106  }
107}