Convert plugin loading code to use Path
All of the internal scanning/loading code now uses Path, as do the plugin and data directories in SitePaths. For backwards compatibility, we still inject the @PluginData directory as a File. We do break backwards compatibility of the ServerPluginProvider extension point, under the theory that there should be very few such extensions and they can handle the pain. Tested: -Installed and reloaded cookbook plugin multiple times. -Expanded dynamic *.ssh plugin loading in the cookbook, with monkey-testing. Change-Id: I5212d248d9288b88d08731c9854b42196999aa73
This commit is contained in:
		| @@ -14,17 +14,17 @@ | ||||
|  | ||||
| package com.google.gerrit.server.plugins; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.util.jar.JarFile; | ||||
|  | ||||
| class CleanupHandle { | ||||
|   private final File tmpFile; | ||||
|   private final Path tmp; | ||||
|   private final JarFile jarFile; | ||||
|  | ||||
|   CleanupHandle(File tmpFile, | ||||
|       JarFile jarFile) { | ||||
|     this.tmpFile = tmpFile; | ||||
|   CleanupHandle(Path tmp, JarFile jarFile) { | ||||
|     this.tmp = tmp; | ||||
|     this.jarFile = jarFile; | ||||
|   } | ||||
|  | ||||
| @@ -34,12 +34,13 @@ class CleanupHandle { | ||||
|     } catch (IOException err) { | ||||
|       PluginLoader.log.error("Cannot close " + jarFile.getName(), err); | ||||
|     } | ||||
|     if (!tmpFile.delete() && tmpFile.exists()) { | ||||
|       PluginLoader.log.warn("Cannot delete " + tmpFile.getAbsolutePath() | ||||
|           + ", retrying to delete it on termination of the virtual machine"); | ||||
|       tmpFile.deleteOnExit(); | ||||
|     } else { | ||||
|       PluginLoader.log.info("Cleaned plugin " + tmpFile.getName()); | ||||
|     try { | ||||
|       Files.deleteIfExists(tmp); | ||||
|       PluginLoader.log.info("Cleaned plugin " + tmp.getFileName()); | ||||
|     } catch (IOException e) { | ||||
|       PluginLoader.log.warn("Cannot delete " + tmp.toAbsolutePath() | ||||
|           + ", retrying to delete it on termination of the virtual machine", e); | ||||
|       tmp.toFile().deleteOnExit(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -24,8 +24,6 @@ import org.eclipse.jgit.internal.storage.file.FileSnapshot; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.net.MalformedURLException; | ||||
| @@ -33,6 +31,7 @@ import java.net.URL; | ||||
| import java.net.URLClassLoader; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.nio.file.Paths; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Date; | ||||
| @@ -45,52 +44,50 @@ public class JarPluginProvider implements ServerPluginProvider { | ||||
|   static final String JAR_EXTENSION = ".jar"; | ||||
|   static final Logger log = LoggerFactory.getLogger(JarPluginProvider.class); | ||||
|  | ||||
|   private final File tmpDir; | ||||
|   private final Path tmpDir; | ||||
|  | ||||
|   @Inject | ||||
|   JarPluginProvider(SitePaths sitePaths) { | ||||
|     // TODO(dborowitz): Convert to NIO. | ||||
|     tmpDir = sitePaths.tmp_dir.toFile(); | ||||
|     tmpDir = sitePaths.tmp_dir; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public boolean handles(File srcFile) { | ||||
|     String fileName = srcFile.getName(); | ||||
|   public boolean handles(Path srcPath) { | ||||
|     String fileName = srcPath.getFileName().toString(); | ||||
|     return fileName.endsWith(JAR_EXTENSION) | ||||
|         || fileName.endsWith(JAR_EXTENSION + ".disabled"); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public String getPluginName(File srcFile) { | ||||
|   public String getPluginName(Path srcPath) { | ||||
|     try { | ||||
|       return MoreObjects.firstNonNull(getJarPluginName(srcFile), | ||||
|           PluginLoader.nameOf(srcFile)); | ||||
|       return MoreObjects.firstNonNull(getJarPluginName(srcPath), | ||||
|           PluginLoader.nameOf(srcPath)); | ||||
|     } catch (IOException e) { | ||||
|       throw new IllegalArgumentException("Invalid plugin file " + srcFile | ||||
|       throw new IllegalArgumentException("Invalid plugin file " + srcPath | ||||
|           + ": cannot get plugin name", e); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public static String getJarPluginName(File srcFile) throws IOException { | ||||
|     try (JarFile jarFile = new JarFile(srcFile)) { | ||||
|   public static String getJarPluginName(Path srcPath) throws IOException { | ||||
|     try (JarFile jarFile = new JarFile(srcPath.toFile())) { | ||||
|       return jarFile.getManifest().getMainAttributes() | ||||
|           .getValue("Gerrit-PluginName"); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public ServerPlugin get(File srcFile, FileSnapshot snapshot, | ||||
|   public ServerPlugin get(Path srcPath, FileSnapshot snapshot, | ||||
|       PluginDescription description) throws InvalidPluginException { | ||||
|     try { | ||||
|       String name = getPluginName(srcFile); | ||||
|       String extension = getExtension(srcFile); | ||||
|       try (FileInputStream in = new FileInputStream(srcFile)) { | ||||
|         File tmp = asTemp(in, tempNameFor(name), extension, tmpDir); | ||||
|         return loadJarPlugin(name, srcFile.toPath(), snapshot, tmp, | ||||
|             description); | ||||
|       String name = getPluginName(srcPath); | ||||
|       String extension = getExtension(srcPath); | ||||
|       try (InputStream in = Files.newInputStream(srcPath)) { | ||||
|         Path tmp = asTemp(in, tempNameFor(name), extension, tmpDir); | ||||
|         return loadJarPlugin(name, srcPath, snapshot, tmp, description); | ||||
|       } | ||||
|     } catch (IOException e) { | ||||
|       throw new InvalidPluginException("Cannot load Jar plugin " + srcFile, e); | ||||
|       throw new InvalidPluginException("Cannot load Jar plugin " + srcPath, e); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -99,8 +96,8 @@ public class JarPluginProvider implements ServerPluginProvider { | ||||
|     return "gerrit"; | ||||
|   } | ||||
|  | ||||
|   private static String getExtension(File file) { | ||||
|     return getExtension(file.getName()); | ||||
|   private static String getExtension(Path path) { | ||||
|     return getExtension(path.getFileName().toString()); | ||||
|   } | ||||
|  | ||||
|   private static String getExtension(String name) { | ||||
| @@ -113,19 +110,18 @@ public class JarPluginProvider implements ServerPluginProvider { | ||||
|     return PLUGIN_TMP_PREFIX + name + "_" + fmt.format(new Date()) + "_"; | ||||
|   } | ||||
|  | ||||
|   public static File storeInTemp(String pluginName, InputStream in, | ||||
|   public static Path storeInTemp(String pluginName, InputStream in, | ||||
|       SitePaths sitePaths) throws IOException { | ||||
|     if (!Files.exists(sitePaths.tmp_dir)) { | ||||
|       Files.createDirectories(sitePaths.tmp_dir); | ||||
|     } | ||||
|     return asTemp(in, tempNameFor(pluginName), ".jar", | ||||
|         sitePaths.tmp_dir.toFile()); | ||||
|     return asTemp(in, tempNameFor(pluginName), ".jar", sitePaths.tmp_dir); | ||||
|   } | ||||
|  | ||||
|   private ServerPlugin loadJarPlugin(String name, Path srcJar, | ||||
|       FileSnapshot snapshot, File tmp, PluginDescription description) | ||||
|       FileSnapshot snapshot, Path tmp, PluginDescription description) | ||||
|       throws IOException, InvalidPluginException, MalformedURLException { | ||||
|     JarFile jarFile = new JarFile(tmp); | ||||
|     JarFile jarFile = new JarFile(tmp.toFile()); | ||||
|     boolean keep = false; | ||||
|     try { | ||||
|       Manifest manifest = jarFile.getManifest(); | ||||
| @@ -134,14 +130,13 @@ public class JarPluginProvider implements ServerPluginProvider { | ||||
|       List<URL> urls = new ArrayList<>(2); | ||||
|       String overlay = System.getProperty("gerrit.plugin-classes"); | ||||
|       if (overlay != null) { | ||||
|         File classes = new File(new File(new File(overlay), name), "main"); | ||||
|         if (classes.isDirectory()) { | ||||
|           log.info(String.format("plugin %s: including %s", name, | ||||
|               classes.getPath())); | ||||
|           urls.add(classes.toURI().toURL()); | ||||
|         Path classes = Paths.get(overlay).resolve(name).resolve("main"); | ||||
|         if (Files.isDirectory(classes)) { | ||||
|           log.info(String.format("plugin %s: including %s", name, classes)); | ||||
|           urls.add(classes.toUri().toURL()); | ||||
|         } | ||||
|       } | ||||
|       urls.add(tmp.toURI().toURL()); | ||||
|       urls.add(tmp.toUri().toURL()); | ||||
|  | ||||
|       ClassLoader pluginLoader = | ||||
|           new URLClassLoader(urls.toArray(new URL[urls.size()]), | ||||
| @@ -149,7 +144,7 @@ public class JarPluginProvider implements ServerPluginProvider { | ||||
|  | ||||
|       JarScanner jarScanner = createJarScanner(srcJar); | ||||
|       ServerPlugin plugin = new ServerPlugin(name, description.canonicalUrl, | ||||
|           description.user, srcJar.toFile(), snapshot, jarScanner, | ||||
|           description.user, srcJar, snapshot, jarScanner, | ||||
|           description.dataDir, pluginLoader); | ||||
|       plugin.setCleanupHandle(new CleanupHandle(tmp, jarFile)); | ||||
|       keep = true; | ||||
|   | ||||
| @@ -18,14 +18,14 @@ import com.google.common.base.Function; | ||||
| import com.google.common.base.Joiner; | ||||
| import com.google.common.collect.Iterables; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.nio.file.Path; | ||||
|  | ||||
| class MultipleProvidersForPluginException extends IllegalArgumentException { | ||||
|   private static final long serialVersionUID = 1L; | ||||
|  | ||||
|   MultipleProvidersForPluginException(File pluginSrcFile, | ||||
|   MultipleProvidersForPluginException(Path pluginSrcPath, | ||||
|       Iterable<ServerPluginProvider> providersHandlers) { | ||||
|     super(pluginSrcFile.getAbsolutePath() | ||||
|     super(pluginSrcPath.toAbsolutePath() | ||||
|         + " is claimed to be handled by more than one plugin provider: " | ||||
|         + providersListToString(providersHandlers)); | ||||
|   } | ||||
|   | ||||
| @@ -14,6 +14,8 @@ | ||||
|  | ||||
| package com.google.gerrit.server.plugins; | ||||
|  | ||||
| import static com.google.gerrit.common.FileUtil.lastModified; | ||||
|  | ||||
| import com.google.common.base.Strings; | ||||
| import com.google.common.collect.Lists; | ||||
| import com.google.gerrit.common.Nullable; | ||||
| @@ -25,7 +27,6 @@ import com.google.inject.Injector; | ||||
|  | ||||
| import org.eclipse.jgit.internal.storage.file.FileSnapshot; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.nio.file.Path; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| @@ -81,18 +82,17 @@ public abstract class Plugin { | ||||
|   private List<ReloadableRegistrationHandle<?>> reloadableHandles; | ||||
|  | ||||
|   public Plugin(String name, | ||||
|       Path srcFile, | ||||
|       Path srcPath, | ||||
|       PluginUser pluginUser, | ||||
|       FileSnapshot snapshot, | ||||
|       ApiType apiType) { | ||||
|     this.name = name; | ||||
|     // TODO(dborowitz): Rename to srcPath or something. | ||||
|     this.srcFile = srcFile; | ||||
|     this.srcFile = srcPath; | ||||
|     this.apiType = apiType; | ||||
|     this.snapshot = snapshot; | ||||
|     this.pluginUser = pluginUser; | ||||
|     this.cacheKey = new Plugin.CacheKey(name); | ||||
|     this.disabled = srcFile.getFileName().toString().endsWith(".disabled"); | ||||
|     this.disabled = srcPath.getFileName().toString().endsWith(".disabled"); | ||||
|   } | ||||
|  | ||||
|   public CleanupHandle getCleanupHandle() { | ||||
| @@ -170,7 +170,7 @@ public abstract class Plugin { | ||||
|  | ||||
|   abstract boolean canReload(); | ||||
|  | ||||
|   boolean isModified(File jar) { | ||||
|     return snapshot.lastModified() != jar.lastModified(); | ||||
|   boolean isModified(Path jar) { | ||||
|     return snapshot.lastModified() != lastModified(jar); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -11,14 +11,15 @@ | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package com.google.gerrit.server.plugins; | ||||
|  | ||||
| import com.google.common.base.Optional; | ||||
|  | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.lang.annotation.Annotation; | ||||
| import java.nio.file.NoSuchFileException; | ||||
| import java.util.Collections; | ||||
| import java.util.Enumeration; | ||||
| import java.util.Map; | ||||
| @@ -57,7 +58,7 @@ public interface PluginContentScanner { | ||||
|  | ||||
|     @Override | ||||
|     public InputStream getInputStream(PluginEntry entry) throws IOException { | ||||
|       throw new FileNotFoundException("Empty plugin"); | ||||
|       throw new NoSuchFileException("Empty plugin"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|   | ||||
| @@ -0,0 +1,38 @@ | ||||
| // Copyright (C) 2015 The Android Open Source Project | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package com.google.gerrit.server.plugins; | ||||
|  | ||||
| import com.google.gerrit.extensions.annotations.PluginData; | ||||
| import com.google.inject.Inject; | ||||
| import com.google.inject.Provider; | ||||
| import com.google.inject.Singleton; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.nio.file.Path; | ||||
|  | ||||
| @Singleton | ||||
| class PluginDataAsFileProvider implements Provider<File> { | ||||
|   private final Provider<Path> pathProvider; | ||||
|  | ||||
|   @Inject | ||||
|   PluginDataAsFileProvider(@PluginData Provider<Path> pathProvider) { | ||||
|     this.pathProvider = pathProvider; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public File get() { | ||||
|     return pathProvider.get().toFile(); | ||||
|   } | ||||
| } | ||||
| @@ -18,6 +18,8 @@ import com.google.common.base.CharMatcher; | ||||
| import com.google.common.base.Joiner; | ||||
| import com.google.common.base.MoreObjects; | ||||
| import com.google.common.base.Predicate; | ||||
| import com.google.common.collect.ComparisonChain; | ||||
| import com.google.common.collect.ImmutableList; | ||||
| import com.google.common.collect.Iterables; | ||||
| import com.google.common.collect.LinkedHashMultimap; | ||||
| import com.google.common.collect.Lists; | ||||
| @@ -25,6 +27,7 @@ import com.google.common.collect.Maps; | ||||
| import com.google.common.collect.Multimap; | ||||
| import com.google.common.collect.Queues; | ||||
| import com.google.common.collect.Sets; | ||||
| import com.google.common.io.ByteStreams; | ||||
| import com.google.gerrit.extensions.annotations.PluginName; | ||||
| import com.google.gerrit.extensions.events.LifecycleListener; | ||||
| import com.google.gerrit.extensions.systemstatus.ServerInformation; | ||||
| @@ -45,15 +48,15 @@ import org.eclipse.jgit.lib.Config; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.FileFilter; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.OutputStream; | ||||
| import java.nio.file.DirectoryStream; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.nio.file.Paths; | ||||
| import java.util.AbstractMap; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.Comparator; | ||||
| @@ -72,13 +75,13 @@ public class PluginLoader implements LifecycleListener { | ||||
|   static final String PLUGIN_TMP_PREFIX = "plugin_"; | ||||
|   static final Logger log = LoggerFactory.getLogger(PluginLoader.class); | ||||
|  | ||||
|   public String getPluginName(File srcFile) { | ||||
|     return MoreObjects.firstNonNull(getGerritPluginName(srcFile), | ||||
|         nameOf(srcFile)); | ||||
|   public String getPluginName(Path srcPath) { | ||||
|     return MoreObjects.firstNonNull(getGerritPluginName(srcPath), | ||||
|         nameOf(srcPath)); | ||||
|   } | ||||
|  | ||||
|   private final File pluginsDir; | ||||
|   private final File dataDir; | ||||
|   private final Path pluginsDir; | ||||
|   private final Path dataDir; | ||||
|   private final PluginGuiceEnvironment env; | ||||
|   private final ServerInformationImpl srvInfoImpl; | ||||
|   private final PluginUser.Factory pluginUserFactory; | ||||
| @@ -159,7 +162,7 @@ public class PluginLoader implements LifecycleListener { | ||||
|     checkRemoteInstall(); | ||||
|  | ||||
|     String fileName = originalName; | ||||
|     File tmp = asTemp(in, ".next_" + fileName + "_", ".tmp", pluginsDir); | ||||
|     Path tmp = asTemp(in, ".next_" + fileName + "_", ".tmp", pluginsDir); | ||||
|     String name = MoreObjects.firstNonNull(getGerritPluginName(tmp), | ||||
|         nameOf(fileName)); | ||||
|     if (!originalName.equals(name)) { | ||||
| @@ -169,26 +172,26 @@ public class PluginLoader implements LifecycleListener { | ||||
|     } | ||||
|  | ||||
|     String fileExtension = getExtension(fileName); | ||||
|     File dst = new File(pluginsDir, name + fileExtension); | ||||
|     Path dst = pluginsDir.resolve(name + fileExtension); | ||||
|     synchronized (this) { | ||||
|       Plugin active = running.get(name); | ||||
|       if (active != null) { | ||||
|         fileName = active.getSrcFile().getFileName().toString(); | ||||
|         log.info(String.format("Replacing plugin %s", active.getName())); | ||||
|         File old = new File(pluginsDir, ".last_" + fileName); | ||||
|         old.delete(); | ||||
|         active.getSrcFile().toFile().renameTo(old); | ||||
|         Path old = pluginsDir.resolve(".last_" + fileName); | ||||
|         Files.deleteIfExists(old); | ||||
|         Files.move(active.getSrcFile(), old); | ||||
|       } | ||||
|  | ||||
|       new File(pluginsDir, fileName + ".disabled").delete(); | ||||
|       tmp.renameTo(dst); | ||||
|       Files.deleteIfExists(pluginsDir.resolve(fileName + ".disabled")); | ||||
|       Files.move(tmp, dst); | ||||
|       try { | ||||
|         Plugin plugin = runPlugin(name, dst, active); | ||||
|         if (active == null) { | ||||
|           log.info(String.format("Installed plugin %s", plugin.getName())); | ||||
|         } | ||||
|       } catch (PluginInstallException e) { | ||||
|         dst.delete(); | ||||
|         Files.deleteIfExists(dst); | ||||
|         throw e; | ||||
|       } | ||||
|  | ||||
| @@ -196,21 +199,17 @@ public class PluginLoader implements LifecycleListener { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   static File asTemp(InputStream in, String prefix, String suffix, File dir) | ||||
|   static Path asTemp(InputStream in, String prefix, String suffix, Path dir) | ||||
|       throws IOException { | ||||
|     File tmp = File.createTempFile(prefix, suffix, dir); | ||||
|     Path tmp = Files.createTempFile(dir, prefix, suffix); | ||||
|     boolean keep = false; | ||||
|     try (FileOutputStream out = new FileOutputStream(tmp)) { | ||||
|       byte[] data = new byte[8192]; | ||||
|       int n; | ||||
|       while ((n = in.read(data)) > 0) { | ||||
|         out.write(data, 0, n); | ||||
|       } | ||||
|     try (OutputStream out = Files.newOutputStream(tmp)) { | ||||
|       ByteStreams.copy(in, out); | ||||
|       keep = true; | ||||
|       return tmp; | ||||
|     } finally { | ||||
|       if (!keep) { | ||||
|         tmp.delete(); | ||||
|         Files.delete(tmp); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| @@ -241,12 +240,21 @@ public class PluginLoader implements LifecycleListener { | ||||
|         } | ||||
|  | ||||
|         log.info(String.format("Disabling plugin %s", active.getName())); | ||||
|         File off = new File(active.getSrcFile() + ".disabled"); | ||||
|         active.getSrcFile().toFile().renameTo(off); | ||||
|         Path off = active.getSrcFile().resolveSibling( | ||||
|             active.getSrcFile().getFileName() + ".disabled"); | ||||
|         try { | ||||
|           Files.move(active.getSrcFile(), off); | ||||
|         } catch (IOException e) { | ||||
|           log.error("Failed to disable plugin", e); | ||||
|           // In theory we could still unload the plugin even if the rename | ||||
|           // failed. However, it would be reloaded on the next server startup, | ||||
|           // which is probably not what the user expects. | ||||
|           continue; | ||||
|         } | ||||
|  | ||||
|         unloadPlugin(active); | ||||
|         try { | ||||
|           FileSnapshot snapshot = FileSnapshot.save(off); | ||||
|           FileSnapshot snapshot = FileSnapshot.save(off.toFile()); | ||||
|           Plugin offPlugin = loadPlugin(name, off, snapshot); | ||||
|           disabled.put(name, offPlugin); | ||||
|         } catch (Throwable e) { | ||||
| @@ -279,9 +287,13 @@ public class PluginLoader implements LifecycleListener { | ||||
|         if (n.endsWith(".disabled")) { | ||||
|           n = n.substring(0, n.lastIndexOf('.')); | ||||
|         } | ||||
|         File on = new File(pluginsDir, n); | ||||
|         off.getSrcFile().toFile().renameTo(on); | ||||
|  | ||||
|         Path on = pluginsDir.resolve(n); | ||||
|         try { | ||||
|           Files.move(off.getSrcFile(), on); | ||||
|         } catch (IOException e) { | ||||
|           log.error("Failed to move plugin " + name + " into place", e); | ||||
|           continue; | ||||
|         } | ||||
|         disabled.remove(name); | ||||
|         runPlugin(name, on, null); | ||||
|       } | ||||
| @@ -291,7 +303,7 @@ public class PluginLoader implements LifecycleListener { | ||||
|  | ||||
|   @Override | ||||
|   public synchronized void start() { | ||||
|     log.info("Loading plugins from " + pluginsDir.getAbsolutePath()); | ||||
|     log.info("Loading plugins from " + pluginsDir.toAbsolutePath()); | ||||
|     srvInfoImpl.state = ServerInformation.State.STARTUP; | ||||
|     rescan(); | ||||
|     srvInfoImpl.state = ServerInformation.State.RUNNING; | ||||
| @@ -343,7 +355,7 @@ public class PluginLoader implements LifecycleListener { | ||||
|         String name = active.getName(); | ||||
|         try { | ||||
|           log.info(String.format("Reloading plugin %s", name)); | ||||
|           runPlugin(name, active.getSrcFile().toFile(), active); | ||||
|           runPlugin(name, active.getSrcFile(), active); | ||||
|         } catch (PluginInstallException e) { | ||||
|           log.warn(String.format("Cannot reload plugin %s", name), e.getCause()); | ||||
|           throw e; | ||||
| @@ -355,30 +367,30 @@ public class PluginLoader implements LifecycleListener { | ||||
|   } | ||||
|  | ||||
|   public synchronized void rescan() { | ||||
|     Multimap<String, File> pluginsFiles = prunePlugins(pluginsDir); | ||||
|     Multimap<String, Path> pluginsFiles = prunePlugins(pluginsDir); | ||||
|     if (pluginsFiles.isEmpty()) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     syncDisabledPlugins(pluginsFiles); | ||||
|  | ||||
|     Map<String, File> activePlugins = filterDisabled(pluginsFiles); | ||||
|     for (Map.Entry<String, File> entry : jarsFirstSortedPluginsSet(activePlugins)) { | ||||
|     Map<String, Path> activePlugins = filterDisabled(pluginsFiles); | ||||
|     for (Map.Entry<String, Path> entry : jarsFirstSortedPluginsSet(activePlugins)) { | ||||
|       String name = entry.getKey(); | ||||
|       File file = entry.getValue(); | ||||
|       String fileName = file.getName(); | ||||
|       if (!isJsPlugin(fileName) && !serverPluginFactory.handles(file)) { | ||||
|       Path path = entry.getValue(); | ||||
|       String fileName = path.getFileName().toString(); | ||||
|       if (!isJsPlugin(fileName) && !serverPluginFactory.handles(path)) { | ||||
|         log.warn("No Plugin provider was found that handles this file format: {}", fileName); | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       FileSnapshot brokenTime = broken.get(name); | ||||
|       if (brokenTime != null && !brokenTime.isModified(file)) { | ||||
|       if (brokenTime != null && !brokenTime.isModified(path.toFile())) { | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       Plugin active = running.get(name); | ||||
|       if (active != null && !active.isModified(file)) { | ||||
|       if (active != null && !active.isModified(path)) { | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
| @@ -388,7 +400,7 @@ public class PluginLoader implements LifecycleListener { | ||||
|       } | ||||
|  | ||||
|       try { | ||||
|         Plugin loadedPlugin = runPlugin(name, file, active); | ||||
|         Plugin loadedPlugin = runPlugin(name, path, active); | ||||
|         if (active == null && !loadedPlugin.isDisabled()) { | ||||
|           log.info(String.format("Loaded plugin %s, version %s", | ||||
|               loadedPlugin.getName(), loadedPlugin.getVersion())); | ||||
| @@ -401,31 +413,28 @@ public class PluginLoader implements LifecycleListener { | ||||
|     cleanInBackground(); | ||||
|   } | ||||
|  | ||||
|   private void addAllEntries(Map<String, File> from, | ||||
|       TreeSet<Entry<String, File>> to) { | ||||
|     Iterator<Entry<String, File>> it = from.entrySet().iterator(); | ||||
|   private void addAllEntries(Map<String, Path> from, | ||||
|       TreeSet<Entry<String, Path>> to) { | ||||
|     Iterator<Entry<String, Path>> it = from.entrySet().iterator(); | ||||
|     while (it.hasNext()) { | ||||
|       Entry<String,File> entry = it.next(); | ||||
|       Entry<String,Path> entry = it.next(); | ||||
|       to.add(new AbstractMap.SimpleImmutableEntry<>( | ||||
|           entry.getKey(), entry.getValue())); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private TreeSet<Entry<String, File>> jarsFirstSortedPluginsSet( | ||||
|       Map<String, File> activePlugins) { | ||||
|     TreeSet<Entry<String, File>> sortedPlugins = | ||||
|         Sets.newTreeSet(new Comparator<Entry<String, File>>() { | ||||
|   private TreeSet<Entry<String, Path>> jarsFirstSortedPluginsSet( | ||||
|       Map<String, Path> activePlugins) { | ||||
|     TreeSet<Entry<String, Path>> sortedPlugins = | ||||
|         Sets.newTreeSet(new Comparator<Entry<String, Path>>() { | ||||
|           @Override | ||||
|           public int compare(Entry<String, File> entry1, | ||||
|               Entry<String, File> entry2) { | ||||
|             String file1 = entry1.getValue().getName(); | ||||
|             String file2 = entry2.getValue().getName(); | ||||
|             int cmp = file1.compareTo(file2); | ||||
|             if (file1.endsWith(".jar")) { | ||||
|               return (file2.endsWith(".jar") ? cmp : -1); | ||||
|             } else { | ||||
|               return (file2.endsWith(".jar") ? +1 : cmp); | ||||
|             } | ||||
|           public int compare(Entry<String, Path> e1, Entry<String, Path> e2) { | ||||
|             Path n1 = e1.getValue().getFileName(); | ||||
|             Path n2 = e2.getValue().getFileName(); | ||||
|             return ComparisonChain.start() | ||||
|                 .compareTrueFirst(n1.endsWith(".jar"), n2.endsWith(".jar")) | ||||
|                 .compare(n1, n2) | ||||
|                 .result(); | ||||
|           } | ||||
|         }); | ||||
|  | ||||
| @@ -433,14 +442,14 @@ public class PluginLoader implements LifecycleListener { | ||||
|     return sortedPlugins; | ||||
|   } | ||||
|  | ||||
|   private void syncDisabledPlugins(Multimap<String, File> jars) { | ||||
|   private void syncDisabledPlugins(Multimap<String, Path> jars) { | ||||
|     stopRemovedPlugins(jars); | ||||
|     dropRemovedDisabledPlugins(jars); | ||||
|   } | ||||
|  | ||||
|   private Plugin runPlugin(String name, File plugin, Plugin oldPlugin) | ||||
|   private Plugin runPlugin(String name, Path plugin, Plugin oldPlugin) | ||||
|       throws PluginInstallException { | ||||
|     FileSnapshot snapshot = FileSnapshot.save(plugin); | ||||
|     FileSnapshot snapshot = FileSnapshot.save(plugin.toFile()); | ||||
|     try { | ||||
|       Plugin newPlugin = loadPlugin(name, plugin, snapshot); | ||||
|       if (newPlugin.getCleanupHandle() != null) { | ||||
| @@ -480,11 +489,11 @@ public class PluginLoader implements LifecycleListener { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private void stopRemovedPlugins(Multimap<String, File> jars) { | ||||
|   private void stopRemovedPlugins(Multimap<String, Path> jars) { | ||||
|     Set<String> unload = Sets.newHashSet(running.keySet()); | ||||
|     for (Map.Entry<String, Collection<File>> entry : jars.asMap().entrySet()) { | ||||
|       for (File file : entry.getValue()) { | ||||
|         if (!file.getName().endsWith(".disabled")) { | ||||
|     for (Map.Entry<String, Collection<Path>> entry : jars.asMap().entrySet()) { | ||||
|       for (Path path : entry.getValue()) { | ||||
|         if (!path.getFileName().toString().endsWith(".disabled")) { | ||||
|           unload.remove(entry.getKey()); | ||||
|         } | ||||
|       } | ||||
| @@ -494,11 +503,11 @@ public class PluginLoader implements LifecycleListener { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private void dropRemovedDisabledPlugins(Multimap<String, File> jars) { | ||||
|   private void dropRemovedDisabledPlugins(Multimap<String, Path> jars) { | ||||
|     Set<String> unload = Sets.newHashSet(disabled.keySet()); | ||||
|     for (Map.Entry<String, Collection<File>> entry : jars.asMap().entrySet()) { | ||||
|       for (File file : entry.getValue()) { | ||||
|         if (file.getName().endsWith(".disabled")) { | ||||
|     for (Map.Entry<String, Collection<Path>> entry : jars.asMap().entrySet()) { | ||||
|       for (Path path : entry.getValue()) { | ||||
|         if (path.getFileName().toString().endsWith(".disabled")) { | ||||
|           unload.remove(entry.getKey()); | ||||
|         } | ||||
|       } | ||||
| @@ -529,8 +538,8 @@ public class PluginLoader implements LifecycleListener { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public static String nameOf(File plugin) { | ||||
|     return nameOf(plugin.getName()); | ||||
|   public static String nameOf(Path plugin) { | ||||
|     return nameOf(plugin.getFileName().toString()); | ||||
|   } | ||||
|  | ||||
|   private static String nameOf(String name) { | ||||
| @@ -546,21 +555,21 @@ public class PluginLoader implements LifecycleListener { | ||||
|     return 0 < ext ? name.substring(ext) : ""; | ||||
|   } | ||||
|  | ||||
|   private Plugin loadPlugin(String name, File srcPlugin, FileSnapshot snapshot) | ||||
|   private Plugin loadPlugin(String name, Path srcPlugin, FileSnapshot snapshot) | ||||
|       throws InvalidPluginException { | ||||
|     String pluginName = srcPlugin.getName(); | ||||
|     String pluginName = srcPlugin.getFileName().toString(); | ||||
|     if (isJsPlugin(pluginName)) { | ||||
|       return loadJsPlugin(name, srcPlugin.toPath(), snapshot); | ||||
|       return loadJsPlugin(name, srcPlugin, snapshot); | ||||
|     } else if (serverPluginFactory.handles(srcPlugin)) { | ||||
|       return loadServerPlugin(srcPlugin, snapshot); | ||||
|     } else { | ||||
|       throw new InvalidPluginException(String.format( | ||||
|           "Unsupported plugin type: %s", srcPlugin.getName())); | ||||
|           "Unsupported plugin type: %s", srcPlugin.getFileName())); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private File getPluginDataDir(String name) { | ||||
|     return new File(dataDir, name); | ||||
|   private Path getPluginDataDir(String name) { | ||||
|     return dataDir.resolve(name); | ||||
|   } | ||||
|  | ||||
|   private String getPluginCanonicalWebUrl(String name) { | ||||
| @@ -574,7 +583,7 @@ public class PluginLoader implements LifecycleListener { | ||||
|     return new JsPlugin(name, srcJar, pluginUserFactory.create(name), snapshot); | ||||
|   } | ||||
|  | ||||
|   private ServerPlugin loadServerPlugin(File scriptFile, | ||||
|   private ServerPlugin loadServerPlugin(Path scriptFile, | ||||
|       FileSnapshot snapshot) throws InvalidPluginException { | ||||
|     String name = serverPluginFactory.getPluginName(scriptFile); | ||||
|     return serverPluginFactory.get(scriptFile, snapshot, new PluginDescription( | ||||
| @@ -598,15 +607,15 @@ public class PluginLoader implements LifecycleListener { | ||||
|  | ||||
|   // Only one active plugin per plugin name can exist for each plugin name. | ||||
|   // Filter out disabled plugins and transform the multimap to a map | ||||
|   private static Map<String, File> filterDisabled( | ||||
|       Multimap<String, File> pluginFiles) { | ||||
|     Map<String, File> activePlugins = Maps.newHashMapWithExpectedSize( | ||||
|         pluginFiles.keys().size()); | ||||
|     for (String name : pluginFiles.keys()) { | ||||
|       for (File pluginFile : pluginFiles.asMap().get(name)) { | ||||
|         if (!pluginFile.getName().endsWith(".disabled")) { | ||||
|   private static Map<String, Path> filterDisabled( | ||||
|       Multimap<String, Path> pluginPaths) { | ||||
|     Map<String, Path> activePlugins = Maps.newHashMapWithExpectedSize( | ||||
|         pluginPaths.keys().size()); | ||||
|     for (String name : pluginPaths.keys()) { | ||||
|       for (Path pluginPath : pluginPaths.asMap().get(name)) { | ||||
|         if (!pluginPath.getFileName().toString().endsWith(".disabled")) { | ||||
|           assert(!activePlugins.containsKey(name)); | ||||
|           activePlugins.put(name, pluginFile); | ||||
|           activePlugins.put(name, pluginPath); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| @@ -622,37 +631,40 @@ public class PluginLoader implements LifecycleListener { | ||||
|   // | ||||
|   // NOTE: Bear in mind that the plugin name can be reassigned after load by the | ||||
|   //       Server plugin provider. | ||||
|   public Multimap<String, File> prunePlugins(File pluginsDir) { | ||||
|     List<File> pluginFiles = scanFilesInPluginsDirectory(pluginsDir); | ||||
|     Multimap<String, File> map; | ||||
|     map = asMultimap(pluginFiles); | ||||
|   public Multimap<String, Path> prunePlugins(Path pluginsDir) { | ||||
|     List<Path> pluginPaths = scanPathsInPluginsDirectory(pluginsDir); | ||||
|     Multimap<String, Path> map; | ||||
|     map = asMultimap(pluginPaths); | ||||
|     for (String plugin : map.keySet()) { | ||||
|       Collection<File> files = map.asMap().get(plugin); | ||||
|       Collection<Path> files = map.asMap().get(plugin); | ||||
|       if (files.size() == 1) { | ||||
|         continue; | ||||
|       } | ||||
|       // retrieve enabled plugins | ||||
|       Iterable<File> enabled = filterDisabledPlugins( | ||||
|           files); | ||||
|       Iterable<Path> enabled = filterDisabledPlugins(files); | ||||
|       // If we have only one (the winner) plugin, nothing to do | ||||
|       if (!Iterables.skip(enabled, 1).iterator().hasNext()) { | ||||
|         continue; | ||||
|       } | ||||
|       File winner = Iterables.getFirst(enabled, null); | ||||
|       Path winner = Iterables.getFirst(enabled, null); | ||||
|       assert(winner != null); | ||||
|       // Disable all loser plugins by renaming their file names to | ||||
|       // "file.disabled" and replace the disabled files in the multimap. | ||||
|       Collection<File> elementsToRemove = Lists.newArrayList(); | ||||
|       Collection<File> elementsToAdd = Lists.newArrayList(); | ||||
|       for (File loser : Iterables.skip(enabled, 1)) { | ||||
|       Collection<Path> elementsToRemove = Lists.newArrayList(); | ||||
|       Collection<Path> elementsToAdd = Lists.newArrayList(); | ||||
|       for (Path loser : Iterables.skip(enabled, 1)) { | ||||
|         log.warn(String.format("Plugin <%s> was disabled, because" | ||||
|              + " another plugin <%s>" | ||||
|              + " with the same name <%s> already exists", | ||||
|              loser, winner, plugin)); | ||||
|         File disabledPlugin = new File(loser + ".disabled"); | ||||
|         Path disabledPlugin = Paths.get(loser + ".disabled"); | ||||
|         elementsToAdd.add(disabledPlugin); | ||||
|         elementsToRemove.add(loser); | ||||
|         loser.renameTo(disabledPlugin); | ||||
|         try { | ||||
|           Files.move(loser, disabledPlugin); | ||||
|         } catch (IOException e) { | ||||
|           log.warn("Failed to fully disable plugin " + loser, e); | ||||
|         } | ||||
|       } | ||||
|       Iterables.removeAll(files, elementsToRemove); | ||||
|       Iterables.addAll(files, elementsToAdd); | ||||
| @@ -660,50 +672,52 @@ public class PluginLoader implements LifecycleListener { | ||||
|     return map; | ||||
|   } | ||||
|  | ||||
|   private List<File> scanFilesInPluginsDirectory(File pluginsDir) { | ||||
|     if (pluginsDir == null || !pluginsDir.exists()) { | ||||
|   private List<Path> scanPathsInPluginsDirectory(Path pluginsDir) { | ||||
|     if (pluginsDir == null || !Files.exists(pluginsDir)) { | ||||
|       return Collections.emptyList(); | ||||
|     } | ||||
|     File[] matches = pluginsDir.listFiles(new FileFilter() { | ||||
|     DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() { | ||||
|       @Override | ||||
|       public boolean accept(File pathname) { | ||||
|         String n = pathname.getName(); | ||||
|       public boolean accept(Path entry) throws IOException { | ||||
|         String n = entry.getFileName().toString(); | ||||
|         return !n.startsWith(".last_") | ||||
|             && !n.startsWith(".next_"); | ||||
|       } | ||||
|     }); | ||||
|     if (matches == null) { | ||||
|       log.error("Cannot list " + pluginsDir.getAbsolutePath()); | ||||
|       return Collections.emptyList(); | ||||
|     }; | ||||
|     try (DirectoryStream<Path> files | ||||
|         = Files.newDirectoryStream(pluginsDir, filter)) { | ||||
|       return ImmutableList.copyOf(files); | ||||
|     } catch (IOException e) { | ||||
|       log.error("Cannot list " + pluginsDir.toAbsolutePath(), e); | ||||
|       return ImmutableList.of(); | ||||
|     } | ||||
|     return Arrays.asList(matches); | ||||
|   } | ||||
|  | ||||
|   private static Iterable<File> filterDisabledPlugins( | ||||
|       Collection<File> files) { | ||||
|     return Iterables.filter(files, new Predicate<File>() { | ||||
|   private static Iterable<Path> filterDisabledPlugins( | ||||
|       Collection<Path> paths) { | ||||
|     return Iterables.filter(paths, new Predicate<Path>() { | ||||
|       @Override | ||||
|       public boolean apply(File file) { | ||||
|         return !file.getName().endsWith(".disabled"); | ||||
|       public boolean apply(Path p) { | ||||
|         return !p.getFileName().toString().endsWith(".disabled"); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   public String getGerritPluginName(File srcFile) { | ||||
|     String fileName = srcFile.getName(); | ||||
|   public String getGerritPluginName(Path srcPath) { | ||||
|     String fileName = srcPath.getFileName().toString(); | ||||
|     if (isJsPlugin(fileName)) { | ||||
|       return fileName.substring(0, fileName.length() - 3); | ||||
|     } | ||||
|     if (serverPluginFactory.handles(srcFile)) { | ||||
|       return serverPluginFactory.getPluginName(srcFile); | ||||
|     if (serverPluginFactory.handles(srcPath)) { | ||||
|       return serverPluginFactory.getPluginName(srcPath); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   private Multimap<String, File> asMultimap(List<File> plugins) { | ||||
|     Multimap<String, File> map = LinkedHashMultimap.create(); | ||||
|     for (File srcFile : plugins) { | ||||
|       map.put(getPluginName(srcFile), srcFile); | ||||
|   private Multimap<String, Path> asMultimap(List<Path> plugins) { | ||||
|     Multimap<String, Path> map = LinkedHashMultimap.create(); | ||||
|     for (Path srcPath : plugins) { | ||||
|       map.put(getPluginName(srcPath), srcPath); | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
|   | ||||
| @@ -36,6 +36,8 @@ import org.eclipse.jgit.internal.storage.file.FileSnapshot; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.util.List; | ||||
| import java.util.jar.Attributes; | ||||
| import java.util.jar.Manifest; | ||||
| @@ -59,7 +61,7 @@ public class ServerPlugin extends Plugin { | ||||
|  | ||||
|   private final Manifest manifest; | ||||
|   private final PluginContentScanner scanner; | ||||
|   private final File dataDir; | ||||
|   private final Path dataDir; | ||||
|   private final String pluginCanonicalWebUrl; | ||||
|   private final ClassLoader classLoader; | ||||
|   private Class<? extends Module> sysModule; | ||||
| @@ -75,12 +77,12 @@ public class ServerPlugin extends Plugin { | ||||
|   public ServerPlugin(String name, | ||||
|       String pluginCanonicalWebUrl, | ||||
|       PluginUser pluginUser, | ||||
|       File srcJar, | ||||
|       Path srcJar, | ||||
|       FileSnapshot snapshot, | ||||
|       PluginContentScanner scanner, | ||||
|       File dataDir, | ||||
|       Path dataDir, | ||||
|       ClassLoader classLoader) throws InvalidPluginException { | ||||
|     super(name, srcJar.toPath(), pluginUser, snapshot, | ||||
|     super(name, srcJar, pluginUser, snapshot, | ||||
|         Plugin.getApiType(getPluginManifest(scanner))); | ||||
|     this.pluginCanonicalWebUrl = pluginCanonicalWebUrl; | ||||
|     this.scanner = scanner; | ||||
| @@ -128,8 +130,8 @@ public class ServerPlugin extends Plugin { | ||||
|     return (Class<? extends Module>) clazz; | ||||
|   } | ||||
|  | ||||
|   File getSrcJar() { | ||||
|     return getSrcFile().toFile(); | ||||
|   Path getSrcJar() { | ||||
|     return getSrcFile(); | ||||
|   } | ||||
|  | ||||
|   private static Manifest getPluginManifest(PluginContentScanner scanner) | ||||
| @@ -245,20 +247,22 @@ public class ServerPlugin extends Plugin { | ||||
|           .annotatedWith(PluginCanonicalWebUrl.class) | ||||
|           .toInstance(pluginCanonicalWebUrl); | ||||
|  | ||||
|         bind(File.class) | ||||
|         bind(Path.class) | ||||
|           .annotatedWith(PluginData.class) | ||||
|           .toProvider(new Provider<File>() { | ||||
|           .toProvider(new Provider<Path>() { | ||||
|             private volatile boolean ready; | ||||
|  | ||||
|             @Override | ||||
|             public File get() { | ||||
|             public Path get() { | ||||
|               if (!ready) { | ||||
|                 synchronized (dataDir) { | ||||
|                   if (!ready) { | ||||
|                     if (!dataDir.exists() && !dataDir.mkdirs()) { | ||||
|                     try { | ||||
|                       Files.createDirectories(dataDir); | ||||
|                     } catch (IOException e) { | ||||
|                       throw new ProvisionException(String.format( | ||||
|                           "Cannot create %s for plugin %s", | ||||
|                           dataDir.getAbsolutePath(), getName())); | ||||
|                           dataDir.toAbsolutePath(), getName()), e); | ||||
|                     } | ||||
|                     ready = true; | ||||
|                   } | ||||
| @@ -267,6 +271,8 @@ public class ServerPlugin extends Plugin { | ||||
|               return dataDir; | ||||
|             } | ||||
|           }); | ||||
|         bind(File.class).annotatedWith(PluginData.class) | ||||
|             .toProvider(PluginDataAsFileProvider.class); | ||||
|       } | ||||
|     }); | ||||
|     return Guice.createInjector(modules); | ||||
|   | ||||
| @@ -19,7 +19,7 @@ import com.google.gerrit.server.PluginUser; | ||||
|  | ||||
| import org.eclipse.jgit.internal.storage.file.FileSnapshot; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.nio.file.Path; | ||||
|  | ||||
| /** | ||||
|  * Provider of one Server plugin from one external file | ||||
| @@ -31,7 +31,6 @@ import java.io.File; | ||||
|  * group them into a directory tree and then load the directory | ||||
|  * root as a single plugin. | ||||
|  */ | ||||
| // TODO(dborowitz): Convert to NIO; ensure clients can migrate. | ||||
| @ExtensionPoint | ||||
| public interface ServerPluginProvider { | ||||
|  | ||||
| @@ -41,7 +40,7 @@ public interface ServerPluginProvider { | ||||
|   public class PluginDescription { | ||||
|     public final PluginUser user; | ||||
|     public final String canonicalUrl; | ||||
|     public final File dataDir; | ||||
|     public final Path dataDir; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new PluginDescription for ServerPluginProvider. | ||||
| @@ -50,7 +49,7 @@ public interface ServerPluginProvider { | ||||
|      * @param canonicalUrl plugin root Web URL | ||||
|      * @param dataDir directory for plugin data | ||||
|      */ | ||||
|     public PluginDescription(PluginUser user, String canonicalUrl, File dataDir) { | ||||
|     public PluginDescription(PluginUser user, String canonicalUrl, Path dataDir) { | ||||
|       this.user = user; | ||||
|       this.canonicalUrl = canonicalUrl; | ||||
|       this.dataDir = dataDir; | ||||
| @@ -60,39 +59,39 @@ public interface ServerPluginProvider { | ||||
|   /** | ||||
|    * Declares the availability to manage an external file or directory | ||||
|    * | ||||
|    * @param srcFile the external file or directory | ||||
|    * @param srcPath the external file or directory | ||||
|    * @return true if file or directory can be loaded into a Server Plugin | ||||
|    */ | ||||
|   boolean handles(File srcFile); | ||||
|   boolean handles(Path srcPath); | ||||
|  | ||||
|   /** | ||||
|    * Returns the plugin name of an external file or directory | ||||
|    * | ||||
|    * Should be called only if {@link #handles(File) handles(srcFile)} | ||||
|    * Should be called only if {@link #handles(Path) handles(srcFile)} | ||||
|    * returns true and thus srcFile is a supported plugin format. | ||||
|    * An IllegalArgumentException is thrown otherwise as srcFile | ||||
|    * is not a valid file format for extracting its plugin name. | ||||
|    * | ||||
|    * @param srcFile external file or directory | ||||
|    * @param srcPath external file or directory | ||||
|    * @return plugin name | ||||
|    */ | ||||
|   String getPluginName(File srcFile); | ||||
|   String getPluginName(Path srcPath); | ||||
|  | ||||
|   /** | ||||
|    * Loads an external file or directory into a Server plugin. | ||||
|    * | ||||
|    * Should be called only if {@link #handles(File) handles(srcFile)} | ||||
|    * Should be called only if {@link #handles(Path) handles(srcFile)} | ||||
|    * returns true and thus srcFile is a supported plugin format. | ||||
|    * An IllegalArgumentException is thrown otherwise as srcFile | ||||
|    * is not a valid file format for extracting its plugin name. | ||||
|    * | ||||
|    * @param srcFile external file or directory | ||||
|    * @param srcPath external file or directory | ||||
|    * @param snapshot snapshot of the external file | ||||
|    * @param pluginDescriptor descriptor of the ServerPlugin to load | ||||
|    * @throws InvalidPluginException if plugin is supposed to be handled | ||||
|    *         but cannot be loaded for any other reason | ||||
|    */ | ||||
|   ServerPlugin get(File srcFile, FileSnapshot snapshot, | ||||
|   ServerPlugin get(Path srcPath, FileSnapshot snapshot, | ||||
|       PluginDescription pluginDescriptor) throws InvalidPluginException; | ||||
|  | ||||
|   /** | ||||
|   | ||||
| @@ -22,7 +22,7 @@ import org.eclipse.jgit.internal.storage.file.FileSnapshot; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.nio.file.Path; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| @@ -38,27 +38,26 @@ class UniversalServerPluginProvider implements ServerPluginProvider { | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public ServerPlugin get(File srcFile, FileSnapshot snapshot, | ||||
|   public ServerPlugin get(Path srcPath, FileSnapshot snapshot, | ||||
|       PluginDescription pluginDescription) throws InvalidPluginException { | ||||
|     return providerOf(srcFile).get(srcFile, snapshot, pluginDescription); | ||||
|     return providerOf(srcPath).get(srcPath, snapshot, pluginDescription); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public String getPluginName(File srcFile) { | ||||
|     return providerOf(srcFile).getPluginName(srcFile); | ||||
|   public String getPluginName(Path srcPath) { | ||||
|     return providerOf(srcPath).getPluginName(srcPath); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public boolean handles(File srcFile) { | ||||
|     List<ServerPluginProvider> providers = | ||||
|         providersForHandlingPlugin(srcFile); | ||||
|   public boolean handles(Path srcPath) { | ||||
|     List<ServerPluginProvider> providers = providersForHandlingPlugin(srcPath); | ||||
|     switch (providers.size()) { | ||||
|       case 1: | ||||
|         return true; | ||||
|       case 0: | ||||
|         return false; | ||||
|       default: | ||||
|         throw new MultipleProvidersForPluginException(srcFile, providers); | ||||
|         throw new MultipleProvidersForPluginException(srcPath, providers); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -67,27 +66,27 @@ class UniversalServerPluginProvider implements ServerPluginProvider { | ||||
|     return "gerrit"; | ||||
|   } | ||||
|  | ||||
|   private ServerPluginProvider providerOf(File srcFile) { | ||||
|   private ServerPluginProvider providerOf(Path srcPath) { | ||||
|     List<ServerPluginProvider> providers = | ||||
|         providersForHandlingPlugin(srcFile); | ||||
|         providersForHandlingPlugin(srcPath); | ||||
|     switch (providers.size()) { | ||||
|       case 1: | ||||
|         return providers.get(0); | ||||
|       case 0: | ||||
|         throw new IllegalArgumentException( | ||||
|             "No ServerPluginProvider found/loaded to handle plugin file " | ||||
|                 + srcFile.getAbsolutePath()); | ||||
|                 + srcPath.toAbsolutePath()); | ||||
|       default: | ||||
|         throw new MultipleProvidersForPluginException(srcFile, providers); | ||||
|         throw new MultipleProvidersForPluginException(srcPath, providers); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private List<ServerPluginProvider> providersForHandlingPlugin( | ||||
|       final File srcFile) { | ||||
|       final Path srcPath) { | ||||
|     List<ServerPluginProvider> providers = new ArrayList<>(); | ||||
|     for (ServerPluginProvider serverPluginProvider : serverPluginProviders) { | ||||
|       boolean handles = serverPluginProvider.handles(srcFile); | ||||
|       log.debug("File {} handled by {} ? => {}", srcFile, | ||||
|       boolean handles = serverPluginProvider.handles(srcPath); | ||||
|       log.debug("File {} handled by {} ? => {}", srcPath, | ||||
|           serverPluginProvider.getProviderPluginName(), handles); | ||||
|       if (handles) { | ||||
|         providers.add(serverPluginProvider); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Dave Borowitz
					Dave Borowitz