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:
Shawn Pearce
2012-05-11 17:33:58 -07:00
committed by gerrit code review
6 changed files with 137 additions and 19 deletions

View File

@@ -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
------ ------

View File

@@ -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

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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
View File

@@ -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>