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 io.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 034 public Set<String> findResourceNames(String location, URL locationUrl) throws IOException { 035 try (JarFile jarFile = getJarFromUrl(locationUrl)) { 036 // For Tomcat and non-expanded WARs. 037 String prefix = jarFile.getName().toLowerCase().endsWith(".war") ? "WEB-INF/classes/" : ""; 038 return findResourceNamesFromJarFile(jarFile, prefix, location); 039 } 040 } 041 042 /** 043 * Retrieves the Jar file represented by this URL. 044 * 045 * @param locationUrl The URL of the jar. 046 * @return The jar file. 047 * @throws IOException when the jar could not be resolved. 048 */ 049 private JarFile getJarFromUrl(URL locationUrl) throws IOException { 050 URLConnection con = locationUrl.openConnection(); 051 if (con instanceof JarURLConnection) { 052 // Should usually be the case for traditional JAR files. 053 JarURLConnection jarCon = (JarURLConnection) con; 054 jarCon.setUseCaches(false); 055 return jarCon.getJarFile(); 056 } 057 058 // No JarURLConnection -> need to resort to URL file parsing. 059 // We'll assume URLs of the format "jar:path!/entry", with the protocol 060 // being arbitrary as long as following the entry format. 061 // We'll also handle paths with and without leading "file:" prefix. 062 String urlFile = locationUrl.getFile(); 063 064 int separatorIndex = urlFile.indexOf("!/"); 065 if (separatorIndex != -1) { 066 String jarFileUrl = urlFile.substring(0, separatorIndex); 067 if (jarFileUrl.startsWith("file:")) { 068 try { 069 return new JarFile(new URL(jarFileUrl).toURI().getSchemeSpecificPart()); 070 } catch (URISyntaxException ex) { 071 // Fallback for URLs that are not valid URIs (should hardly ever happen). 072 return new JarFile(jarFileUrl.substring("file:".length())); 073 } 074 } 075 return new JarFile(jarFileUrl); 076 } 077 078 return new JarFile(urlFile); 079 } 080 081 /** 082 * Finds all the resource names contained in this directory within this jar file. 083 * 084 * @param jarFile The jar file. 085 * @param prefix The prefix to ignore within the jar file. 086 * @param location The location to look under. 087 * @return The resource names. 088 */ 089 private Set<String> findResourceNamesFromJarFile(JarFile jarFile, String prefix, String location) { 090 String toScan = prefix + location + (location.endsWith("/") ? "" : "/"); 091 Set<String> resourceNames = new TreeSet<>(); 092 093 Enumeration<JarEntry> entries = jarFile.entries(); 094 while (entries.hasMoreElements()) { 095 String entryName = entries.nextElement().getName(); 096 if (entryName.startsWith(toScan)) { 097 resourceNames.add(entryName.substring(prefix.length())); 098 } 099 } 100 101 return resourceNames; 102 } 103}