JavaScipt and GWT based UI plugin support

Adds support for extending Gerrit's web UI using either pure
JavaScript or GWT compiled with a modified CrossSiteIframeLinker.

To add code to the web UI plugins should bind WebUiPlugin to either
GwtPlugin or JavaScriptPlugin in their plugin's Web-Module. This
tells the UI bootstrap code where to find additional JavaScript to
inject into the page before displaying content.

For a GWT based plugin:

  * com.google.gerrit.client.Plugin class should be extended,
  * inheritance from com.google.gerrit.Plugin should be added
    to gwt.xml,
  * subclass of client.Plugin should be set as entry point,
  * dependency from com.google.gerrit/gerrit-plugin-gwtui should
    be added to pom.xml,
  * webappDirectory option for gwt-maven-plugin should be
    set to ${project.build.directory}/classes/static

For JavaScript based plugins the source code should be put into
the static/ directory of the plugin's JAR file.

Currently there are no extension points for either JS or GWT plugins
to interact with. Some will be added in a future commit. With this
commit only simple modification of the UI is possible, for example
calling window.alert() or adding extra elements to page contents
based on the existing element ids.

Change-Id: I3558991ba4a1477d6cde64eb0a761a7e4c399ec3
Signed-off-by: Dariusz Luksza <dariusz@luksza.org>
This commit is contained in:
Dariusz Luksza
2012-10-10 11:10:09 +02:00
committed by Shawn O. Pearce
parent bc16fa2f3f
commit cd4fc1c239
21 changed files with 793 additions and 39 deletions

View File

@@ -0,0 +1,23 @@
<!--
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.
-->
<module>
<define-linker name="gerrit_plugin" class="com.google.gerrit.linker.GerritPluginLinker"/>
<add-linker name="gerrit_plugin"/>
<generate-with class="com.google.gerrit.rebind.PluginGenerator">
<when-type-assignable class="com.google.gerrit.client.Plugin"/>
</generate-with>
<source path="client"/>
</module>

View File

@@ -0,0 +1,29 @@
// 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.client;
import com.google.gwt.core.client.EntryPoint;
/**
* Base class for writing Gerrit Web UI plugins
*
* Writing a plugin:
* <ol>
* <li>Declare subtype of Plugin</li>
* <li>Bind WebUiPlugin to GwtPlugin implementation in Gerrit-Module</li>
* </ol>
*/
public abstract class Plugin implements EntryPoint {
}

View File

@@ -0,0 +1,31 @@
// 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.linker;
import com.google.gwt.core.ext.LinkerContext;
import com.google.gwt.core.linker.CrossSiteIframeLinker;
/** Finalizes the module manifest file with the selection script. */
public final class GerritPluginLinker extends CrossSiteIframeLinker {
@Override
public String getDescription() {
return "Gerrit GWT UI plugin";
}
@Override
protected String getJsComputeUrlForResource(LinkerContext context) {
return "com/google/gerrit/linker/computeUrlForPluginResource.js";
}
}

View File

@@ -0,0 +1,95 @@
// Copyright (C) 2012 The Android Open Source Project
// Copyright 2008 Google Inc.
//
// 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.rebind;
import java.io.PrintWriter;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
/**
* Write the top layer in the Gadget bootstrap sandwich and generate a stub
* manifest that will be completed by the linker.
*
* Based on gwt-gadgets GadgetGenerator class
*/
public class PluginGenerator extends Generator {
@Override
public String generate(TreeLogger logger, GeneratorContext context,
String typeName) throws UnableToCompleteException {
// The TypeOracle knows about all types in the type system
TypeOracle typeOracle = context.getTypeOracle();
// Get a reference to the type that the generator should implement
JClassType sourceType = typeOracle.findType(typeName);
// Ensure that the requested type exists
if (sourceType == null) {
logger.log(TreeLogger.ERROR, "Could not find requested typeName", null);
throw new UnableToCompleteException();
}
// Make sure the Gadget type is correctly defined
validateType(logger, sourceType);
// Pick a name for the generated class to not conflict.
String generatedSimpleSourceName = sourceType.getSimpleSourceName()
+ "PluginImpl";
// Begin writing the generated source.
ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(
sourceType.getPackage().getName(), generatedSimpleSourceName);
f.addImport(GWT.class.getName());
f.setSuperclass(typeName);
// All source gets written through this Writer
PrintWriter out = context.tryCreate(logger,
sourceType.getPackage().getName(), generatedSimpleSourceName);
// If an implementation already exists, we don't need to do any work
if (out != null) {
// We really use a SourceWriter since it's convenient
SourceWriter sw = f.createSourceWriter(context, out);
sw.println("public " + generatedSimpleSourceName + "() {");
sw.indent();
sw.println("onModuleLoad();");
sw.outdent();
sw.println("}");
sw.commit(logger);
}
return f.getCreatedClassName();
}
protected void validateType(TreeLogger logger, JClassType type)
throws UnableToCompleteException {
if (!type.isDefaultInstantiable()) {
logger.log(TreeLogger.ERROR, "Plugin types must be default instantiable",
null);
throw new UnableToCompleteException();
}
}
}