Merge changes I29e1b1c5,I22bbe366
* changes: Auto-format plugin HTTP pages from known formats Update the plugins doc with docs/deployement info
This commit is contained in:
@@ -5,35 +5,57 @@ A plugin in gerrit is tightly coupled code that runs in the same
|
||||
JVM as gerrit. It has full access to all gerrit internals. Plugins
|
||||
are coupled to a specific major.minor gerrit version.
|
||||
|
||||
REQUIREMENTS
|
||||
Requirements
|
||||
------------
|
||||
|
||||
To start development, you may download the sample maven project, which downloads
|
||||
the following dependencies;
|
||||
To start development, download the sample maven project, which downloads the
|
||||
following dependencies:
|
||||
|
||||
* gerrit-sdk.jar file that matches the war file you are developing against
|
||||
* gerrit-sdk.jar file that matches the war file to develop against
|
||||
|
||||
|
||||
Manifest
|
||||
--------
|
||||
|
||||
Plugins need to include the following data in the jar manifest file;
|
||||
Gerrit-Plugin = plugin_name
|
||||
Gerrit-Module = pkg.class
|
||||
Plugins need to include the following data in the jar manifest file:
|
||||
|
||||
Gerrit-Module = pkg.class
|
||||
|
||||
Optionally include:
|
||||
|
||||
Gerrit-ReloadMode = 'reload' (default) or 'restart'
|
||||
|
||||
If the plugin holds an exclusive resource that must be released before loading
|
||||
the plugin again, ReloadMode must be set to 'restart'. Otherwise 'reload' is
|
||||
sufficient.
|
||||
|
||||
SSH Commands
|
||||
------------
|
||||
|
||||
You may develop plugins which provide commands that can be accessed through the SSH interface.
|
||||
These commands register themselves as a part of SSH Commands (link).
|
||||
Plugins may provide commands that can be accessed through the SSH interface.
|
||||
These commands register themselves as a part of link:cmd-index.html[SSH Commands].
|
||||
|
||||
Each of your plugins commands needs to extend BaseCommand.
|
||||
Each of the plugin commands needs to extend SshCommand.
|
||||
|
||||
Any plugin which implements at least one ssh command needs to also provide a class which extends
|
||||
the PluginCommandModule in order to register the ssh command(s) in its configure method which you
|
||||
must override.
|
||||
Any plugin which implements at least one ssh command needs to also provide a
|
||||
class which extends the PluginCommandModule in order to register the ssh
|
||||
command(s) in its configure method which must be overriden.
|
||||
|
||||
Registering is done by calling the command(String commandName).to(ClassName<? extends BaseCommand> klass)
|
||||
Registering is done by calling:
|
||||
|
||||
command(String commandName).to(ClassName<? extends SshCommand> klass)
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Place files into Documentation/ or static/ and package them into the plugin jar
|
||||
to access them in a browser via <canonicalWebURL>/plugins/<pluginName>/...
|
||||
|
||||
Deployment
|
||||
----------
|
||||
|
||||
Deploy plugins into <review_site>/plugins/. The file name in that directory will
|
||||
be the plugin name on the server.
|
||||
|
||||
GERRIT
|
||||
------
|
||||
|
||||
@@ -52,6 +52,7 @@ Included Components
|
||||
|JSR 305 | <<jsr305,New-Style BSD>>
|
||||
|dk.brics.automaton | <<automaton,New-Style BSD>>
|
||||
|Java Concurrency in Practice Annotations | <<jcip,Create Commons Attribution License>>
|
||||
|pegdown | <<apache2,Apache License 2.0>>
|
||||
|======================================================================
|
||||
|
||||
Cryptography Notice
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gerrit.extensions.registration.RegistrationHandle;
|
||||
import com.google.gerrit.server.documentation.MarkdownFormatter;
|
||||
import com.google.gerrit.server.MimeUtilFileTypeRegistry;
|
||||
import com.google.gerrit.server.plugins.Plugin;
|
||||
import com.google.gerrit.server.plugins.ReloadPluginListener;
|
||||
@@ -173,8 +174,15 @@ class HttpPluginServlet extends HttpServlet
|
||||
if (file.startsWith("Documentation/") || file.startsWith("static/")) {
|
||||
JarFile jar = holder.plugin.getJarFile();
|
||||
JarEntry entry = jar.getJarEntry(file);
|
||||
if (entry != null && entry.getSize() > 0) {
|
||||
sendResource(jar, entry, res);
|
||||
if (file.startsWith("Documentation/") && !isValidEntry(entry)) {
|
||||
entry = getRealFileEntry(jar, file);
|
||||
if (isValidEntry(entry)) {
|
||||
sendResource(jar, entry, res, holder.plugin.getName(), true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isValidEntry(entry)) {
|
||||
sendResource(jar, entry, res, holder.plugin.getName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -183,8 +191,24 @@ class HttpPluginServlet extends HttpServlet
|
||||
res.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
}
|
||||
|
||||
private void sendResource(JarFile jar, JarEntry entry, HttpServletResponse res)
|
||||
private JarEntry getRealFileEntry(JarFile jar, String file) {
|
||||
// TODO: Replace with a loop iterating over possible formatters
|
||||
return jar.getJarEntry(file.replaceAll("\\.html$", ".md"));
|
||||
}
|
||||
|
||||
private boolean isValidEntry(JarEntry entry) {
|
||||
return entry != null && entry.getSize() > 0;
|
||||
}
|
||||
|
||||
private void sendResource(JarFile jar, JarEntry entry,
|
||||
HttpServletResponse res, String pluginName) throws IOException {
|
||||
sendResource(jar, entry, res, pluginName, false);
|
||||
}
|
||||
|
||||
private void sendResource(JarFile jar, JarEntry entry,
|
||||
HttpServletResponse res, String pluginName, boolean format)
|
||||
throws IOException {
|
||||
String entryName = entry.getName();
|
||||
byte[] data = null;
|
||||
if (entry.getSize() <= 128 * 1024) {
|
||||
data = new byte[(int) entry.getSize()];
|
||||
@@ -194,24 +218,44 @@ class HttpPluginServlet extends HttpServlet
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
} else if (format == true) {
|
||||
log.warn(String.format("Plugin '%s' file '%s' too large to format",
|
||||
pluginName, entryName));
|
||||
}
|
||||
|
||||
String contentType = null;
|
||||
String charEnc = null;
|
||||
Attributes atts = entry.getAttributes();
|
||||
if (atts != null) {
|
||||
contentType = Strings.emptyToNull(atts.getValue("Content-Type"));
|
||||
charEnc = Strings.emptyToNull(atts.getValue("Character-Encoding"));
|
||||
}
|
||||
|
||||
if (contentType == null) {
|
||||
MimeType type = mimeUtil.getMimeType(entry.getName(), data);
|
||||
MimeType type = mimeUtil.getMimeType(entryName, data);
|
||||
contentType = type.toString();
|
||||
}
|
||||
|
||||
if (format && data != null) {
|
||||
if (charEnc == null) {
|
||||
charEnc = "UTF-8";
|
||||
}
|
||||
MarkdownFormatter fmter = new MarkdownFormatter();
|
||||
data = fmter.getHtmlFromMarkdown(data, charEnc);
|
||||
res.setHeader("Content-Length", Long.toString(data.length));
|
||||
contentType = "text/html";
|
||||
} else {
|
||||
res.setHeader("Content-Length", Long.toString(entry.getSize()));
|
||||
}
|
||||
|
||||
long time = entry.getTime();
|
||||
if (0 < time) {
|
||||
res.setDateHeader("Last-Modified", time);
|
||||
}
|
||||
res.setContentType(contentType);
|
||||
res.setHeader("Content-Length", Long.toString(entry.getSize()));
|
||||
if (charEnc != null) {
|
||||
res.setCharacterEncoding(charEnc);
|
||||
}
|
||||
if (data != null) {
|
||||
res.getOutputStream().write(data);
|
||||
} else {
|
||||
|
||||
@@ -170,6 +170,11 @@ limitations under the License.
|
||||
<groupId>com.googlecode.prolog-cafe</groupId>
|
||||
<artifactId>PrologCafe</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.pegdown</groupId>
|
||||
<artifactId>pegdown</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (C) 2012 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.documentation;
|
||||
|
||||
import static org.pegdown.Extensions.ALL;
|
||||
|
||||
import org.eclipse.jgit.util.RawParseUtils;
|
||||
import org.pegdown.PegDownProcessor;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class MarkdownFormatter {
|
||||
|
||||
public byte[] getHtmlFromMarkdown(byte[] data, String charEnc)
|
||||
throws UnsupportedEncodingException {
|
||||
String decodedData = RawParseUtils.decode(Charset.forName(charEnc), data);
|
||||
String formatted = new PegDownProcessor(ALL).markdownToHtml(decodedData);
|
||||
data = formatted.getBytes(charEnc);
|
||||
return data;
|
||||
}
|
||||
// TODO: Add a cache
|
||||
}
|
||||
11
pom.xml
11
pom.xml
@@ -822,6 +822,12 @@ limitations under the License.
|
||||
<artifactId>PrologCafe</artifactId>
|
||||
<version>1.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.pegdown</groupId>
|
||||
<artifactId>pegdown</artifactId>
|
||||
<version>1.1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -850,5 +856,10 @@ limitations under the License.
|
||||
<id>clojars-repo</id>
|
||||
<url>http://clojars.org/repo</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>scala-tools</id>
|
||||
<url>http://scala-tools.org/repo-releases</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
</project>
|
||||
|
||||
Reference in New Issue
Block a user