001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * Sonar is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019 */
020package org.sonar.server.plugins;
021
022import com.google.common.base.*;
023import com.google.common.collect.Lists;
024import org.apache.commons.io.FileUtils;
025import org.apache.commons.lang.CharEncoding;
026import org.apache.commons.lang.StringUtils;
027import org.slf4j.LoggerFactory;
028
029import javax.annotation.Nullable;
030import java.io.File;
031import java.io.IOException;
032import java.net.URL;
033import java.net.URLDecoder;
034import java.util.Collection;
035import java.util.Enumeration;
036import java.util.jar.JarEntry;
037import java.util.jar.JarFile;
038
039/**
040 * @since 3.0
041 */
042public final class ClassLoaderUtils {
043
044  private ClassLoaderUtils() {
045  }
046
047  public static File copyResources(ClassLoader classLoader, String rootPath, File toDir) {
048    return copyResources(classLoader, rootPath, toDir, Functions.<String>identity());
049  }
050
051  public static File copyResources(ClassLoader classLoader, String rootPath, File toDir, Function<String, String> relocationFunction) {
052    Collection<String> relativePaths = listFiles(classLoader, rootPath);
053    for (String relativePath : relativePaths) {
054      URL resource = classLoader.getResource(relativePath);
055      String filename = relocationFunction.apply(relativePath);
056      File toFile = new File(toDir, filename);
057      try {
058        FileUtils.copyURLToFile(resource, toFile);
059      } catch (IOException e) {
060        throw new IllegalStateException("Fail to extract " + relativePath + " to " + toFile.getAbsolutePath());
061      }
062    }
063
064    return toDir;
065  }
066
067  /**
068   * Finds files within a given directory and its subdirectories
069   *
070   * @param classLoader
071   * @param rootPath    the root directory, for example org/sonar/sqale
072   * @return a list of relative paths, for example {"org/sonar/sqale/foo/bar.txt}. Never null.
073   */
074  public static Collection<String> listFiles(ClassLoader classLoader, String rootPath) {
075    return listResources(classLoader, rootPath, new Predicate<String>() {
076      public boolean apply(@Nullable String path) {
077        return !StringUtils.endsWith(path, "/");
078      }
079    });
080  }
081
082
083  public static Collection<String> listResources(ClassLoader classLoader, String rootPath) {
084    return listResources(classLoader, rootPath, Predicates.<String>alwaysTrue());
085  }
086
087  /**
088   * Finds directories and files within a given directory and its subdirectories.
089   *
090   * @param classLoader
091   * @param rootPath    the root directory, for example org/sonar/sqale, or a file in this root directory, for example org/sonar/sqale/index.txt
092   * @param
093   * @return a list of relative paths, for example {"org/sonar/sqale", "org/sonar/sqale/foo", "org/sonar/sqale/foo/bar.txt}. Never null.
094   */
095  public static Collection<String> listResources(ClassLoader classLoader, String rootPath, Predicate<String> predicate) {
096    String jarPath = null;
097    JarFile jar = null;
098    try {
099      Collection<String> paths = Lists.newArrayList();
100      rootPath = StringUtils.removeStart(rootPath, "/");
101
102      URL root = classLoader.getResource(rootPath);
103      if (root != null) {
104        checkJarFile(root);
105
106        // Path of the root directory
107        // Examples :
108        // org/sonar/sqale/index.txt  -> rootDirectory is org/sonar/sqale
109        // org/sonar/sqale/  -> rootDirectory is org/sonar/sqale
110        // org/sonar/sqale  -> rootDirectory is org/sonar/sqale
111        String rootDirectory = rootPath;
112        if (StringUtils.substringAfterLast(rootPath, "/").indexOf('.') >= 0) {
113          rootDirectory = StringUtils.substringBeforeLast(rootPath, "/");
114        }
115        jarPath = root.getPath().substring(5, root.getPath().indexOf("!")); //strip out only the JAR file
116        jar = new JarFile(URLDecoder.decode(jarPath, CharEncoding.UTF_8));
117        Enumeration<JarEntry> entries = jar.entries();
118        while (entries.hasMoreElements()) {
119          String name = entries.nextElement().getName();
120          if (name.startsWith(rootDirectory) && predicate.apply(name)) {
121            paths.add(name);
122          }
123        }
124      }
125      return paths;
126    } catch (Exception e) {
127      throw Throwables.propagate(e);
128    } finally {
129      closeJar(jar, jarPath);
130    }
131  }
132
133  private static void closeJar(JarFile jar, String jarPath) {
134    if (jar != null) {
135      try {
136        jar.close();
137      } catch (Exception e) {
138        LoggerFactory.getLogger(ClassLoaderUtils.class).error("Fail to close JAR file: " + jarPath, e);
139      }
140    }
141  }
142
143  private static void checkJarFile(URL root) {
144    if (!"jar".equals(root.getProtocol())) {
145      throw new IllegalStateException("Unsupported protocol: " + root.getProtocol());
146    }
147  }
148}