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
|
JVM as gerrit. It has full access to all gerrit internals. Plugins
|
||||||
are coupled to a specific major.minor gerrit version.
|
are coupled to a specific major.minor gerrit version.
|
||||||
|
|
||||||
REQUIREMENTS
|
Requirements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
To start development, you may download the sample maven project, which downloads
|
To start development, download the sample maven project, which downloads the
|
||||||
the following dependencies;
|
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
|
Manifest
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Plugins need to include the following data in the jar manifest file;
|
Plugins need to include the following data in the jar manifest file:
|
||||||
Gerrit-Plugin = plugin_name
|
|
||||||
Gerrit-Module = pkg.class
|
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
|
SSH Commands
|
||||||
------------
|
------------
|
||||||
|
|
||||||
You may develop plugins which provide commands that can be accessed through the SSH interface.
|
Plugins may provide commands that can be accessed through the SSH interface.
|
||||||
These commands register themselves as a part of SSH Commands (link).
|
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
|
Any plugin which implements at least one ssh command needs to also provide a
|
||||||
the PluginCommandModule in order to register the ssh command(s) in its configure method which you
|
class which extends the PluginCommandModule in order to register the ssh
|
||||||
must override.
|
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
|
GERRIT
|
||||||
------
|
------
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ Included Components
|
|||||||
|JSR 305 | <<jsr305,New-Style BSD>>
|
|JSR 305 | <<jsr305,New-Style BSD>>
|
||||||
|dk.brics.automaton | <<automaton,New-Style BSD>>
|
|dk.brics.automaton | <<automaton,New-Style BSD>>
|
||||||
|Java Concurrency in Practice Annotations | <<jcip,Create Commons Attribution License>>
|
|Java Concurrency in Practice Annotations | <<jcip,Create Commons Attribution License>>
|
||||||
|
|pegdown | <<apache2,Apache License 2.0>>
|
||||||
|======================================================================
|
|======================================================================
|
||||||
|
|
||||||
Cryptography Notice
|
Cryptography Notice
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import com.google.common.base.Strings;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.gerrit.extensions.registration.RegistrationHandle;
|
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.MimeUtilFileTypeRegistry;
|
||||||
import com.google.gerrit.server.plugins.Plugin;
|
import com.google.gerrit.server.plugins.Plugin;
|
||||||
import com.google.gerrit.server.plugins.ReloadPluginListener;
|
import com.google.gerrit.server.plugins.ReloadPluginListener;
|
||||||
@@ -173,8 +174,15 @@ class HttpPluginServlet extends HttpServlet
|
|||||||
if (file.startsWith("Documentation/") || file.startsWith("static/")) {
|
if (file.startsWith("Documentation/") || file.startsWith("static/")) {
|
||||||
JarFile jar = holder.plugin.getJarFile();
|
JarFile jar = holder.plugin.getJarFile();
|
||||||
JarEntry entry = jar.getJarEntry(file);
|
JarEntry entry = jar.getJarEntry(file);
|
||||||
if (entry != null && entry.getSize() > 0) {
|
if (file.startsWith("Documentation/") && !isValidEntry(entry)) {
|
||||||
sendResource(jar, entry, res);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,8 +191,24 @@ class HttpPluginServlet extends HttpServlet
|
|||||||
res.sendError(HttpServletResponse.SC_NOT_FOUND);
|
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 {
|
throws IOException {
|
||||||
|
String entryName = entry.getName();
|
||||||
byte[] data = null;
|
byte[] data = null;
|
||||||
if (entry.getSize() <= 128 * 1024) {
|
if (entry.getSize() <= 128 * 1024) {
|
||||||
data = new byte[(int) entry.getSize()];
|
data = new byte[(int) entry.getSize()];
|
||||||
@@ -194,24 +218,44 @@ class HttpPluginServlet extends HttpServlet
|
|||||||
} finally {
|
} finally {
|
||||||
in.close();
|
in.close();
|
||||||
}
|
}
|
||||||
|
} else if (format == true) {
|
||||||
|
log.warn(String.format("Plugin '%s' file '%s' too large to format",
|
||||||
|
pluginName, entryName));
|
||||||
}
|
}
|
||||||
|
|
||||||
String contentType = null;
|
String contentType = null;
|
||||||
|
String charEnc = null;
|
||||||
Attributes atts = entry.getAttributes();
|
Attributes atts = entry.getAttributes();
|
||||||
if (atts != null) {
|
if (atts != null) {
|
||||||
contentType = Strings.emptyToNull(atts.getValue("Content-Type"));
|
contentType = Strings.emptyToNull(atts.getValue("Content-Type"));
|
||||||
|
charEnc = Strings.emptyToNull(atts.getValue("Character-Encoding"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contentType == null) {
|
if (contentType == null) {
|
||||||
MimeType type = mimeUtil.getMimeType(entry.getName(), data);
|
MimeType type = mimeUtil.getMimeType(entryName, data);
|
||||||
contentType = type.toString();
|
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();
|
long time = entry.getTime();
|
||||||
if (0 < time) {
|
if (0 < time) {
|
||||||
res.setDateHeader("Last-Modified", time);
|
res.setDateHeader("Last-Modified", time);
|
||||||
}
|
}
|
||||||
res.setContentType(contentType);
|
res.setContentType(contentType);
|
||||||
res.setHeader("Content-Length", Long.toString(entry.getSize()));
|
if (charEnc != null) {
|
||||||
|
res.setCharacterEncoding(charEnc);
|
||||||
|
}
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
res.getOutputStream().write(data);
|
res.getOutputStream().write(data);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -170,6 +170,11 @@ limitations under the License.
|
|||||||
<groupId>com.googlecode.prolog-cafe</groupId>
|
<groupId>com.googlecode.prolog-cafe</groupId>
|
||||||
<artifactId>PrologCafe</artifactId>
|
<artifactId>PrologCafe</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.pegdown</groupId>
|
||||||
|
<artifactId>pegdown</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<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>
|
<artifactId>PrologCafe</artifactId>
|
||||||
<version>1.3</version>
|
<version>1.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.pegdown</groupId>
|
||||||
|
<artifactId>pegdown</artifactId>
|
||||||
|
<version>1.1.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
@@ -850,5 +856,10 @@ limitations under the License.
|
|||||||
<id>clojars-repo</id>
|
<id>clojars-repo</id>
|
||||||
<url>http://clojars.org/repo</url>
|
<url>http://clojars.org/repo</url>
|
||||||
</repository>
|
</repository>
|
||||||
|
|
||||||
|
<repository>
|
||||||
|
<id>scala-tools</id>
|
||||||
|
<url>http://scala-tools.org/repo-releases</url>
|
||||||
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
Reference in New Issue
Block a user