001/* 002 * Copyright (C) 2012 eXo Platform SAS. 003 * 004 * This is free software; you can redistribute it and/or modify it 005 * under the terms of the GNU Lesser General Public License as 006 * published by the Free Software Foundation; either version 2.1 of 007 * the License, or (at your option) any later version. 008 * 009 * This software is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public 015 * License along with this software; if not, write to the Free 016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 018 */ 019 020package org.crsh.vfs.spi.url; 021 022import org.crsh.util.InputStreamFactory; 023import org.crsh.util.Safe; 024import org.crsh.util.ZipIterator; 025 026import java.io.FileInputStream; 027import java.io.IOException; 028import java.io.InputStream; 029import java.net.URISyntaxException; 030import java.net.URL; 031import java.util.ArrayList; 032import java.util.Collections; 033import java.util.Enumeration; 034import java.util.HashMap; 035import java.util.Iterator; 036import java.util.LinkedList; 037import java.util.zip.ZipEntry; 038 039/** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */ 040public class Node implements Iterable<Resource> { 041 042 /** . */ 043 public final String name; 044 045 /** . */ 046 HashMap<String, Node> children = new HashMap<String, Node>(); 047 048 /** . */ 049 LinkedList<Resource> resources = new LinkedList<Resource>(); 050 051 public Node() { 052 this.name = ""; 053 } 054 055 private Node(String name) { 056 this.name = name; 057 } 058 059 void merge(ClassLoader loader) throws IOException, URISyntaxException { 060 061 // Get the root class path files 062 for (Enumeration<URL> i = loader.getResources("");i.hasMoreElements();) { 063 URL url = i.nextElement(); 064 // In some case we can get null (Tomcat 8) 065 if (url != null) { 066 mergeEntries(url); 067 } 068 } 069 ArrayList<URL> items = Collections.list(loader.getResources("META-INF/MANIFEST.MF")); 070 for (URL item : items) { 071 if ("jar".equals(item.getProtocol())) { 072 String path = item.getPath(); 073 int pos = path.lastIndexOf("!/"); 074 URL url = new URL("jar:" + path.substring(0, pos + 2)); 075 mergeEntries(url); 076 } 077 else { 078 // 079 } 080 } 081 } 082 083 /** 084 * Rewrite an URL by analysing the serie of trailing <code>!/</code>. The number of <code>jar:</code> prefixes 085 * does not have to be equals to the number of separators. 086 * 087 * @param url the url to rewrite 088 * @return the rewritten URL 089 */ 090 String rewrite(String url) { 091 int end = url.lastIndexOf("!/"); 092 if (end >= 0) { 093 String entry = url.substring(end + 2); 094 int start = url.indexOf(':'); 095 String protocol = url.substring(0, start); 096 String nestedURL; 097 if (protocol.equals("jar")) { 098 nestedURL = rewrite(url.substring(start + 1, end)); 099 return "jar:" + nestedURL + "!/" + entry; 100 } else { 101 nestedURL = rewrite(url.substring(0, end)); 102 } 103 return "jar:" + nestedURL + "!/" + entry; 104 } else { 105 return url; 106 } 107 } 108 109 void mergeEntries(URL url) throws IOException, URISyntaxException { 110 // We handle a special case of spring-boot URLs here before diving in the recursive analysis 111 // see https://github.com/spring-projects/spring-boot/tree/master/spring-boot-tools/spring-boot-loader#urls 112 if (url.getProtocol().equals("jar")) { 113 url = new URL(rewrite(url.toString())); 114 } 115 _mergeEntries(url); 116 } 117 118 private void _mergeEntries(URL url) throws IOException, URISyntaxException { 119 if (url.getProtocol().equals("file")) { 120 try { 121 java.io.File f = new java.io.File(url.toURI()); 122 if (f.isDirectory()) { 123 merge(f); 124 } else if (f.getName().endsWith(".jar")) { 125 mergeEntries(new URL("jar:" + url + "!/")); 126 } else { 127 // WTF ? 128 } 129 } 130 catch (URISyntaxException e) { 131 throw new IOException(e); 132 } 133 } 134 else if (url.getProtocol().equals("jar")) { 135 int pos = url.getPath().lastIndexOf("!/"); 136 URL jarURL = new URL(url.getPath().substring(0, pos)); 137 String path = url.getPath().substring(pos + 2); 138 ZipIterator i = ZipIterator.create(jarURL); 139 try { 140 while (i.hasNext()) { 141 ZipEntry entry = i.next(); 142 if (entry.getName().startsWith(path)) { 143 addEntry(url, entry.getName().substring(path.length()), i.getStreamFactory()); 144 } 145 } 146 } 147 finally { 148 Safe.close(i); 149 } 150 } 151 else { 152 if (url.getPath().endsWith(".jar")) { 153 mergeEntries(new URL("jar:" + url + "!/")); 154 } else { 155 // WTF ? 156 } 157 } 158 } 159 160 private void merge(java.io.File f) throws IOException { 161 java.io.File[] files = f.listFiles(); 162 if (files != null) { 163 for (final java.io.File file : files) { 164 String name = file.getName(); 165 Node child = children.get(name); 166 if (file.isDirectory()) { 167 if (child == null) { 168 Node dir = new Node(name); 169 dir.merge(file); 170 children.put(name, dir); 171 } else { 172 child.merge(file); 173 } 174 } else { 175 if (child == null) { 176 children.put(name, child = new Node(name)); 177 } 178 child.resources.add( 179 new Resource(file.toURI().toURL(), 180 new InputStreamFactory() { 181 public InputStream open() throws IOException { 182 return new FileInputStream(file); 183 } 184 }, file.lastModified() 185 ) 186 ); 187 } 188 } 189 } 190 } 191 192 private void addEntry(URL baseURL, String entryName, InputStreamFactory resolver) throws IOException { 193 if (entryName.length() > 0 && entryName.charAt(entryName.length() - 1) != '/') { 194 addEntry(baseURL, 0, entryName, 1, resolver); 195 } 196 } 197 198 private void addEntry(URL baseURL, int index, String entryName, long lastModified, InputStreamFactory resolver) throws IOException { 199 int next = entryName.indexOf('/', index); 200 if (next == -1) { 201 String name = entryName.substring(index); 202 Node child = children.get(name); 203 if (child == null) { 204 children.put(name, child = new Node(name)); 205 } 206 child.resources.add(new Resource(new URL(baseURL + entryName), resolver, lastModified)); 207 } 208 else { 209 String name = entryName.substring(index, next); 210 Node child = children.get(name); 211 if (child == null) { 212 children.put(name, child = new Node(name)); 213 } 214 child.addEntry(baseURL, next + 1, entryName, lastModified, resolver); 215 } 216 } 217 218 @Override 219 public Iterator<Resource> iterator() { 220 return resources.iterator(); 221 } 222}