summaryrefslogtreecommitdiffstats
path: root/java/com/google/gerrit/server/plugins/JarPluginProvider.java
blob: 82f97c95e67981324711ffb2f552005d8919985a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Copyright (C) 2014 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.common.base.MoreObjects;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.registration.PluginName;
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
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;
import java.util.List;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import org.eclipse.jgit.internal.storage.file.FileSnapshot;

public class JarPluginProvider implements ServerPluginProvider {
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  static final String PLUGIN_TMP_PREFIX = "plugin_";
  static final String JAR_EXTENSION = ".jar";

  private final Path tmpDir;
  private final PluginConfigFactory configFactory;

  @Inject
  JarPluginProvider(SitePaths sitePaths, PluginConfigFactory configFactory) {
    this.tmpDir = sitePaths.tmp_dir;
    this.configFactory = configFactory;
  }

  @Override
  public boolean handles(Path srcPath) {
    String fileName = srcPath.getFileName().toString();
    return fileName.endsWith(JAR_EXTENSION) || fileName.endsWith(JAR_EXTENSION + ".disabled");
  }

  @Override
  public String getPluginName(Path srcPath) {
    try {
      return MoreObjects.firstNonNull(getJarPluginName(srcPath), PluginUtil.nameOf(srcPath));
    } catch (IOException e) {
      throw new IllegalArgumentException(
          "Invalid plugin file " + srcPath + ": cannot get plugin name", e);
    }
  }

  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(Path srcPath, FileSnapshot snapshot, PluginDescription description)
      throws InvalidPluginException {
    try {
      String name = getPluginName(srcPath);
      String extension = getExtension(srcPath);
      try (InputStream in = Files.newInputStream(srcPath)) {
        Path tmp = PluginUtil.asTemp(in, tempNameFor(name), extension, tmpDir);
        return loadJarPlugin(name, srcPath, snapshot, tmp, description);
      }
    } catch (IOException e) {
      throw new InvalidPluginException("Cannot load Jar plugin " + srcPath, e);
    }
  }

  @Override
  public String getProviderPluginName() {
    return PluginName.GERRIT;
  }

  private static String getExtension(Path path) {
    return getExtension(path.getFileName().toString());
  }

  private static String getExtension(String name) {
    int ext = name.lastIndexOf('.');
    return 0 < ext ? name.substring(ext) : "";
  }

  private static String tempNameFor(String name) {
    SimpleDateFormat fmt = new SimpleDateFormat("yyMMdd_HHmm");
    return PLUGIN_TMP_PREFIX + name + "_" + fmt.format(new Date()) + "_";
  }

  public static Path storeInTemp(String pluginName, InputStream in, SitePaths sitePaths)
      throws IOException {
    return PluginUtil.asTemp(in, tempNameFor(pluginName), ".jar", sitePaths.tmp_dir);
  }

  private ServerPlugin loadJarPlugin(
      String name, Path srcJar, FileSnapshot snapshot, Path tmp, PluginDescription description)
      throws IOException, InvalidPluginException, MalformedURLException {
    JarFile jarFile = new JarFile(tmp.toFile());
    boolean keep = false;
    try {
      Manifest manifest = jarFile.getManifest();
      Plugin.ApiType type = Plugin.getApiType(manifest);

      List<URL> urls = new ArrayList<>(2);
      String overlay = System.getProperty("gerrit.plugin-classes");
      if (overlay != null) {
        Path classes = Paths.get(overlay).resolve(name).resolve("main");
        if (Files.isDirectory(classes)) {
          logger.atInfo().log("plugin %s: including %s", name, classes);
          urls.add(classes.toUri().toURL());
        }
      }
      urls.add(tmp.toUri().toURL());

      ClassLoader pluginLoader =
          URLClassLoader.newInstance(
              urls.toArray(new URL[urls.size()]), PluginUtil.parentFor(type));

      JarScanner jarScanner = createJarScanner(tmp);
      PluginConfig pluginConfig = configFactory.getFromGerritConfig(name);

      ServerPlugin plugin =
          new ServerPlugin(
              name,
              description.canonicalUrl,
              description.user,
              srcJar,
              snapshot,
              jarScanner,
              description.dataDir,
              pluginLoader,
              pluginConfig.getString("metricsPrefix", null),
              description.gerritRuntime);
      plugin.setCleanupHandle(new CleanupHandle(tmp, jarFile));
      keep = true;
      return plugin;
    } finally {
      if (!keep) {
        jarFile.close();
      }
    }
  }

  private JarScanner createJarScanner(Path srcJar) throws InvalidPluginException {
    try {
      return new JarScanner(srcJar);
    } catch (IOException e) {
      throw new InvalidPluginException("Cannot scan plugin file " + srcJar, e);
    }
  }
}