Line data Source code
1 : // Copyright (C) 2014 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.plugins; 16 : 17 : import com.google.common.base.MoreObjects; 18 : import com.google.common.flogger.FluentLogger; 19 : import com.google.gerrit.extensions.registration.PluginName; 20 : import com.google.gerrit.server.config.PluginConfig; 21 : import com.google.gerrit.server.config.PluginConfigFactory; 22 : import com.google.gerrit.server.config.SitePaths; 23 : import com.google.inject.Inject; 24 : import java.io.IOException; 25 : import java.io.InputStream; 26 : import java.net.MalformedURLException; 27 : import java.net.URL; 28 : import java.net.URLClassLoader; 29 : import java.nio.file.Files; 30 : import java.nio.file.Path; 31 : import java.nio.file.Paths; 32 : import java.time.Instant; 33 : import java.time.ZoneId; 34 : import java.time.format.DateTimeFormatter; 35 : import java.util.ArrayList; 36 : import java.util.List; 37 : import java.util.jar.JarFile; 38 : import java.util.jar.Manifest; 39 : import org.eclipse.jgit.internal.storage.file.FileSnapshot; 40 : 41 : public class JarPluginProvider implements ServerPluginProvider { 42 3 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 43 : 44 : static final String PLUGIN_TMP_PREFIX = "plugin_"; 45 : static final String JAR_EXTENSION = ".jar"; 46 : 47 : private final Path tmpDir; 48 : private final PluginConfigFactory configFactory; 49 : 50 : @Inject 51 3 : JarPluginProvider(SitePaths sitePaths, PluginConfigFactory configFactory) { 52 3 : this.tmpDir = sitePaths.tmp_dir; 53 3 : this.configFactory = configFactory; 54 3 : } 55 : 56 : @Override 57 : public boolean handles(Path srcPath) { 58 3 : String fileName = srcPath.getFileName().toString(); 59 3 : return fileName.endsWith(JAR_EXTENSION) || fileName.endsWith(JAR_EXTENSION + ".disabled"); 60 : } 61 : 62 : @Override 63 : public String getPluginName(Path srcPath) { 64 : try { 65 1 : return MoreObjects.firstNonNull(getJarPluginName(srcPath), PluginUtil.nameOf(srcPath)); 66 0 : } catch (IOException e) { 67 0 : throw new IllegalArgumentException( 68 : "Invalid plugin file " + srcPath + ": cannot get plugin name", e); 69 : } 70 : } 71 : 72 : public static String getJarPluginName(Path srcPath) throws IOException { 73 1 : try (JarFile jarFile = new JarFile(srcPath.toFile())) { 74 1 : return jarFile.getManifest().getMainAttributes().getValue("Gerrit-PluginName"); 75 : } 76 : } 77 : 78 : @Override 79 : public ServerPlugin get(Path srcPath, FileSnapshot snapshot, PluginDescription description) 80 : throws InvalidPluginException { 81 : try { 82 1 : String name = getPluginName(srcPath); 83 1 : String extension = getExtension(srcPath); 84 1 : try (InputStream in = Files.newInputStream(srcPath)) { 85 1 : Path tmp = PluginUtil.asTemp(in, tempNameFor(name), extension, tmpDir); 86 1 : return loadJarPlugin(name, srcPath, snapshot, tmp, description); 87 : } 88 0 : } catch (IOException e) { 89 0 : throw new InvalidPluginException("Cannot load Jar plugin " + srcPath, e); 90 : } 91 : } 92 : 93 : @Override 94 : public String getProviderPluginName() { 95 3 : return PluginName.GERRIT; 96 : } 97 : 98 : private static String getExtension(Path path) { 99 1 : return getExtension(path.getFileName().toString()); 100 : } 101 : 102 : private static String getExtension(String name) { 103 1 : int ext = name.lastIndexOf('.'); 104 1 : return 0 < ext ? name.substring(ext) : ""; 105 : } 106 : 107 : private static String tempNameFor(String name) { 108 1 : DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyMMdd_HHmm").withZone(ZoneId.of("UTC")); 109 1 : return PLUGIN_TMP_PREFIX + name + "_" + fmt.format(Instant.now()) + "_"; 110 : } 111 : 112 : public static Path storeInTemp(String pluginName, InputStream in, SitePaths sitePaths) 113 : throws IOException { 114 0 : return PluginUtil.asTemp(in, tempNameFor(pluginName), ".jar", sitePaths.tmp_dir); 115 : } 116 : 117 : private ServerPlugin loadJarPlugin( 118 : String name, Path srcJar, FileSnapshot snapshot, Path tmp, PluginDescription description) 119 : throws IOException, InvalidPluginException, MalformedURLException { 120 1 : JarFile jarFile = new JarFile(tmp.toFile()); 121 1 : boolean keep = false; 122 : try { 123 1 : Manifest manifest = jarFile.getManifest(); 124 1 : Plugin.ApiType type = Plugin.getApiType(manifest); 125 : 126 1 : List<URL> urls = new ArrayList<>(2); 127 1 : String overlay = System.getProperty("gerrit.plugin-classes"); 128 1 : if (overlay != null) { 129 0 : Path classes = Paths.get(overlay).resolve(name).resolve("main"); 130 0 : if (Files.isDirectory(classes)) { 131 0 : logger.atInfo().log("plugin %s: including %s", name, classes); 132 0 : urls.add(classes.toUri().toURL()); 133 : } 134 : } 135 1 : urls.add(tmp.toUri().toURL()); 136 : 137 1 : ClassLoader pluginLoader = 138 1 : URLClassLoader.newInstance( 139 1 : urls.toArray(new URL[urls.size()]), PluginUtil.parentFor(type)); 140 : 141 1 : JarScanner jarScanner = createJarScanner(tmp); 142 1 : PluginConfig pluginConfig = configFactory.getFromGerritConfig(name); 143 : 144 1 : ServerPlugin plugin = 145 : new ServerPlugin( 146 : name, 147 : description.canonicalUrl, 148 : description.user, 149 : srcJar, 150 : snapshot, 151 : jarScanner, 152 : description.dataDir, 153 : pluginLoader, 154 1 : pluginConfig.getString("metricsPrefix", null), 155 : description.gerritRuntime); 156 1 : plugin.setCleanupHandle(new CleanupHandle(tmp, jarFile)); 157 1 : keep = true; 158 1 : return plugin; 159 : } finally { 160 1 : if (!keep) { 161 0 : jarFile.close(); 162 : } 163 : } 164 : } 165 : 166 : private JarScanner createJarScanner(Path srcJar) throws InvalidPluginException { 167 : try { 168 1 : return new JarScanner(srcJar); 169 0 : } catch (IOException e) { 170 0 : throw new InvalidPluginException("Cannot scan plugin file " + srcJar, e); 171 : } 172 : } 173 : }