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}