Inital commit for its-phabricator
This plugin is based on its-bugzilla's
1a9e450e102842cae6dc225fe230da2eab0b7cc5
Change-Id: I34a4b3c49385dbb3dfc050095f867262c244cea6
diff --git a/BUCK b/BUCK
index 9a3e8dc..aa5978f 100644
--- a/BUCK
+++ b/BUCK
@@ -1,18 +1,18 @@
gerrit_plugin(
- name = 'its-bugzilla',
+ name = 'its-phabricator',
srcs = glob(['src/main/java/**/*.java']),
resources = glob(['src/main/resources/**/*']),
manifest_entries = [
- 'Gerrit-PluginName: its-bugzilla',
- 'Gerrit-Module: com.googlesource.gerrit.plugins.hooks.bz.BugzillaModule',
- 'Gerrit-InitStep: com.googlesource.gerrit.plugins.hooks.bz.InitBugzilla',
+ 'Gerrit-Module: com.googlesource.gerrit.plugins.its.phabricator.PhabricatorModule',
'Gerrit-ReloadMode: reload',
- 'Implementation-Title: Plugin its-bugzilla',
+ 'Implementation-Title: Plugin its-phabricator',
'Implementation-URL: https://www.wikimediafoundation.org',
],
deps = [
':its-base_stripped',
- '//plugins/its-bugzilla/lib:j2bugzilla',
+ '//lib/httpcomponents:httpcore',
+ '//lib/httpcomponents:httpclient',
+ '//lib:gson',
],
)
@@ -48,18 +48,18 @@
]
)
-TEST_UTIL_SRC = glob(['src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/**/*.java'])
+TEST_UTIL_SRC = glob(['src/test/java/com/googlesource/gerrit/plugins/its/testutil/**/*.java'])
java_library(
- name = 'its-bugzilla_tests-utils',
+ name = 'its-phabricator_tests-utils',
srcs = TEST_UTIL_SRC,
deps = [
'//lib:guava',
- '//lib/easymock:easymock',
+ '//plugins/its-phabricator/lib:easymock',
'//lib/log:impl_log4j',
'//lib/log:log4j',
'//lib:junit',
- '//lib/powermock:powermock-api-easymock',
+ '//plugins/its-phabricator/lib:powermock-api-easymock',
'//lib/powermock:powermock-api-support',
'//lib/powermock:powermock-core',
'//lib/powermock:powermock-module-junit4',
@@ -68,18 +68,18 @@
)
java_test(
- name = 'its-bugzilla_tests',
+ name = 'its-phabricator_tests',
srcs = glob(
['src/test/java/**/*.java'],
excludes = TEST_UTIL_SRC
),
- labels = ['its-bugzilla'],
- source_under_test = [':its-bugzilla__plugin'],
+ labels = ['its-phabricator'],
+ source_under_test = [':its-phabricator__plugin'],
deps = [
- ':its-bugzilla__plugin',
- ':its-bugzilla_tests-utils',
+ ':its-phabricator__plugin',
+ ':its-phabricator_tests-utils',
'//gerrit-plugin-api:lib',
- '//lib/easymock:easymock',
+ '//plugins/its-phabricator/lib:easymock',
'//lib:guava',
'//lib/guice:guice',
'//lib/jgit:jgit',
@@ -87,9 +87,13 @@
'//lib/log:api',
'//lib/log:impl_log4j',
'//lib/log:log4j',
- '//lib/powermock:powermock-api-easymock',
+ '//plugins/its-phabricator/lib:powermock-api-easymock',
'//lib/powermock:powermock-api-support',
'//lib/powermock:powermock-core',
'//lib/powermock:powermock-module-junit4',
+ '//lib/powermock:powermock-module-junit4-common',
+ '//lib/powermock:powermock-reflect',
+ '//lib/httpcomponents:httpclient',
+ '//lib:gson',
],
)
diff --git a/lib/BUCK b/lib/BUCK
index b889b94..ea00ba3 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -1,43 +1,38 @@
include_defs('//lib/maven.defs')
+# Upstream is at 3.2, which does not work with PowerMock 1.5's expectNew.
+# So we force EasyMock 3.1.
maven_jar(
- name = 'j2bugzilla',
- id = 'com.j2bugzilla:j2bugzilla:2.2',
- sha1 = 'abd2c9290e6e4ec3222c89a99c79d9c5bbc79300',
- license = 'Apache2.0',
- deps = [':xmlrpc-client'],
+ name = 'easymock',
+ id = 'org.easymock:easymock:3.1',
+ sha1 = '3e127311a86fc2e8f550ef8ee4abe094bbcf7e7e',
+ license = 'DO_NOT_DISTRIBUTE',
+ deps = [
+ '//lib/easymock:cglib-2_2',
+ ':objenesis',
+ ],
)
+# Duplicate upstream's objenesis, which would not be
+# visible otherwise.
maven_jar(
- name = 'xmlrpc-client',
- id = 'org.apache.xmlrpc:xmlrpc-client:3.1.3',
- sha1 = 'e486ad917028b52265610206fb5a1e2b5914b94b',
- license = 'Apache2.0',
- deps = [':xmlrpc-common'],
- visibility = [],
+ name = 'objenesis',
+ id = 'org.objenesis:objenesis:1.2',
+ sha1 = 'bfcb0539a071a4c5a30690388903ac48c0667f2a',
+ license = 'DO_NOT_DISTRIBUTE',
+ visibility = ['//lib/powermock:powermock-reflect'],
+ attach_source = False,
)
+# Upstream's powermock-api-easymock would pull in upstream's
+# EasyMock 3.2, so we hard-wire dependency to the plugin's EasyMock.
maven_jar(
- name = 'xmlrpc-common',
- id = 'org.apache.xmlrpc:xmlrpc-common:3.1.3',
- sha1 = '415daf1f1473a947452588906dc9f5b3575fb44d',
- license = 'Apache2.0',
- deps = [':ws-commons-util'],
- visibility = [],
-)
-
-maven_jar(
- name = 'ws-commons-util',
- id = 'org.apache.ws.commons.util:ws-commons-util:1.0.2',
- sha1 = '3f478e6def772c19d1053f61198fa1f6a6119238',
- license = 'Apache2.0',
- deps = [':xml-apis'],
- visibility = [],
-)
-
-maven_jar(
- name = 'xml-apis',
- id = 'xml-apis:xml-apis:1.0.b2',
- sha1 = '3136ca936f64c9d68529f048c2618bd356bf85c9',
- license = 'Apache2.0',
+ name = 'powermock-api-easymock',
+ id = 'org.powermock:powermock-api-easymock:1.5',
+ sha1 = 'a485b570b9debb46b53459a8e866a40343b2cfe2',
+ license = 'DO_NOT_DISTRIBUTE',
+ deps = [
+ '//lib/powermock:powermock-api-support',
+ ':easymock',
+ ],
)
diff --git a/pom.xml b/pom.xml
index 50f3652..aa9c2b9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,20 +19,18 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
- <groupId>com.googlesource.gerrit.plugins.bugzilla</groupId>
- <artifactId>its-bugzilla</artifactId>
+ <groupId>com.googlesource.gerrit.plugins.phabricator</groupId>
+ <artifactId>its-phabricator</artifactId>
<version>2.11-SNAPSHOT</version>
- <name>Gerrit - Bugzilla support</name>
+ <name>Gerrit - Phabricator support</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<Gerrit-ApiType>plugin</Gerrit-ApiType>
<Gerrit-ReloadMode>reload</Gerrit-ReloadMode>
- <Gerrit-PluginName>its-bugzilla</Gerrit-PluginName>
- <Gerrit-InitStep>com.googlesource.gerrit.plugins.hooks.bz.InitBugzilla</Gerrit-InitStep>
- <Gerrit-Module>com.googlesource.gerrit.plugins.hooks.bz.BugzillaModule</Gerrit-Module>
- <easymockVersion>3.0</easymockVersion>
+ <Gerrit-Module>com.googlesource.gerrit.plugins.its.phabricator.PhabricatorModule</Gerrit-Module>
+ <easymockVersion>3.1</easymockVersion>
<powermockVersion>1.5</powermockVersion>
<slf4jVersion>1.6.2</slf4jVersion>
</properties>
@@ -101,9 +99,14 @@
<version>${project.version}</version>
</dependency>
<dependency>
- <groupId>com.j2bugzilla</groupId>
- <artifactId>j2bugzilla</artifactId>
- <version>2.2</version>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>4.3.4</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>2.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaClient.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaClient.java
deleted file mode 100644
index 770ed96..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaClient.java
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright (C) 2013 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.googlesource.gerrit.plugins.hooks.bz;
-
-import java.util.HashMap;
-import java.util.Set;
-
-import com.googlesource.gerrit.plugins.hooks.its.InvalidTransitionException;
-import com.j2bugzilla.base.Bug;
-import com.j2bugzilla.base.BugzillaConnector;
-import com.j2bugzilla.base.BugzillaException;
-import com.j2bugzilla.base.ConnectionException;
-import com.j2bugzilla.rpc.BugzillaVersion;
-import com.j2bugzilla.rpc.CommentBug;
-import com.j2bugzilla.rpc.GetBug;
-import com.j2bugzilla.rpc.GetLegalValues;
-import com.j2bugzilla.rpc.GetLegalValues.Fields;
-import com.j2bugzilla.rpc.LogIn;
-import com.j2bugzilla.rpc.LogOut;
-import com.j2bugzilla.rpc.UpdateBug;
-
-public class BugzillaClient {
-
- private final BugzillaConnector connector;
- private final String xmlRpcUrl;
- private static HashMap<Fields, Set<String>> legalFieldValues = new HashMap<Fields, Set<String>>();
-
- public BugzillaClient(final String baseUrl) throws ConnectionException {
- this(baseUrl, "/xmlrpc.cgi");
- }
-
- public BugzillaClient(final String baseUrl, final String rpcPath) throws ConnectionException {
- xmlRpcUrl = baseUrl + rpcPath;
- connector = new BugzillaConnector();
- connector.connectTo(xmlRpcUrl);
- }
-
- public String login(final String username, final String password) throws BugzillaException {
- LogIn logIn = new LogIn(username, password);
- connector.executeMethod(logIn);
- return "username="+username+", userid="+logIn.getUserID();
- }
-
- public void logout() throws BugzillaException {
- connector.executeMethod(new LogOut());
- }
-
- public Bug getBug(int bugId) throws BugzillaException {
- GetBug getBugId = new GetBug(bugId);
- connector.executeMethod(getBugId);
- return getBugId.getBug();
- }
-
- public Bug getBug(String bugId) throws BugzillaException {
- return getBug(Integer.parseInt(bugId));
- }
-
- public void addComment(String bugId, String comment) throws BugzillaException {
- CommentBug bugComment = new CommentBug(getBug(bugId), comment);
- connector.executeMethod(bugComment);
- }
-
- private void performSimpleActionChainable(final Bug bug, final String actionName,
- final String actionValue) throws BugzillaException,
- InvalidTransitionException {
- if ("status".equals(actionName)) {
- assertLegalValue(Fields.STATUS, actionValue);
- bug.setStatus(actionValue);
- } else if ("resolution".equals(actionName)) {
- assertLegalValue(Fields.RESOLUTION, actionValue);
- bug.setResolution(actionValue);
- } else {
- throw new InvalidTransitionException("Simple action " + actionName
- + " is not known");
- }
- }
-
- private void performChainedAction(final Bug bug, final String actionName,
- final String actionValue) throws BugzillaException,
- InvalidTransitionException {
- String[] actionNames = actionName.split("/");
- String[] actionValues = actionValue.split("/");
- if (actionNames.length != actionValues.length) {
- throw new InvalidTransitionException("Number of chained actions does not"
- + " match number of action values");
- }
-
- int i;
- for (i=0; i<actionNames.length; i++) {
- performSimpleActionChainable(bug, actionNames[i], actionValues[i]);
- }
- }
-
- public void performAction(final String bugId, final String actionNameParam,
- final String actionValueParam) throws BugzillaException,
- InvalidTransitionException {
- Bug bug = getBug(bugId);
-
- String actionName = actionNameParam;
- if (actionName.startsWith("set-")) {
- actionName = actionName.substring(4);
- }
- actionName = actionName.replaceAll("-and-", "/");
-
- String actionValue = actionValueParam;
- actionValue = actionValue.replaceAll(" ", "/");
-
- if ("status".equals(actionName) || "resolution".equals(actionName)) {
- performSimpleActionChainable(bug, actionName, actionValue);
- } else if ("status/resolution".equals(actionName)) {
- performChainedAction(bug, actionName, actionValue);
- } else {
- throw new InvalidTransitionException("Action " + actionNameParam
- + " is not known");
- }
- connector.executeMethod(new UpdateBug(bug));
- }
-
- private void assertLegalValue(Fields field, String actionValue)
- throws BugzillaException, InvalidTransitionException {
- if (!getLegalValues(field).contains(actionValue)) {
- throw new InvalidTransitionException( "The value '" + actionValue
- + "' is not an allowed value for bugzilla's " + field.getInternalName()
- + " field");
- }
- }
-
- public String getServerVersion() throws BugzillaException {
- BugzillaVersion versionCheck = new BugzillaVersion();
- connector.executeMethod(versionCheck);
- return versionCheck.getVersion();
- }
-
- public String getXmlRpcUrl() {
- return xmlRpcUrl;
- }
-
- private Set<String> getLegalValues(Fields field) throws BugzillaException {
- if (legalFieldValues.get(field) == null) {
- GetLegalValues getValues = new GetLegalValues(field);
- connector.executeMethod(getValues);
- legalFieldValues.put(field, getValues.getLegalValues());
- }
- return legalFieldValues.get(field);
- }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaItsFacade.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaItsFacade.java
deleted file mode 100644
index d4957cf..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaItsFacade.java
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright (C) 2013 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.googlesource.gerrit.plugins.hooks.bz;
-
-import java.io.IOException;
-import java.net.URL;
-import java.util.concurrent.Callable;
-
-import org.eclipse.jgit.lib.Config;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.inject.Inject;
-
-import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-import com.j2bugzilla.base.BugzillaException;
-import com.j2bugzilla.base.ConnectionException;
-
-public class BugzillaItsFacade implements ItsFacade {
- private static final String GERRIT_CONFIG_USERNAME = "username";
- private static final String GERRIT_CONFIG_PASSWORD = "password";
- private static final String GERRIT_CONFIG_URL = "url";
-
- private static final int MAX_ATTEMPTS = 3;
-
- private Logger log = LoggerFactory.getLogger(BugzillaItsFacade.class);
-
- private final String pluginName;
- private Config gerritConfig;
-
- private BugzillaClient client;
-
- @Inject
- public BugzillaItsFacade(@PluginName String pluginName,
- @GerritServerConfig Config cfg) {
- this.pluginName = pluginName;
- try {
- this.gerritConfig = cfg;
- log.info("Connected to Bugzilla at " + client().getXmlRpcUrl()
- + ", reported version is " + client().getServerVersion());
- } catch (Exception ex) {
- log.warn("Bugzilla is currently not available", ex);
- }
- }
-
- @Override
- public String healthCheck(final Check check) throws IOException {
- return execute(new Callable<String>(){
- @Override
- public String call() throws Exception {
- if (check.equals(Check.ACCESS))
- return healthCheckAccess();
- else
- return healthCheckSysinfo();
- }});
- }
-
- @Override
- public void addComment(final String bugId, final String comment) throws IOException {
-
- execute(new Callable<String>(){
- @Override
- public String call() throws Exception {
- log.debug("Adding comment " + comment + " to bug " + bugId);
- client().addComment(bugId, comment);
- log.debug("Added comment " + comment + " to bug " + bugId);
- return bugId;
- }});
- }
-
- @Override
- public void addRelatedLink(final String issueKey, final URL relatedUrl, String description)
- throws IOException {
- addComment(issueKey, "Related URL: " + createLinkForWebui(relatedUrl.toExternalForm(), description));
- }
-
- @Override
- public void performAction(final String bugId, final String actionString)
- throws IOException {
-
- execute(new Callable<String>(){
- @Override
- public String call() throws Exception {
- String actionName = actionString.substring(0, actionString.indexOf(" "));
- String actionValue = actionString.substring(actionString.indexOf(" ") + 1);
- doPerformAction(bugId, actionName, actionValue);
- return bugId;
- }});
- }
-
- private void doPerformAction(final String bugId, final String fieldName, final String fieldValue)
- throws BugzillaException, IOException {
- client().performAction(bugId, fieldName.toLowerCase(), fieldValue);
- }
-
- @Override
- public boolean exists(final String bugId) throws IOException {
- return execute(new Callable<Boolean>(){
- @Override
- public Boolean call() throws Exception {
- return client().getBug(bugId) != null;
- }});
- }
-
- public void logout() {
- this.logout(false);
- }
-
- public void logout(boolean quiet) {
- try {
- client().logout();
- }
- catch (Exception ex) {
- if (!quiet) log.error("I was unable to logout", ex);
- }
- }
-
- public Object login() {
- return login(false);
- }
-
- public Object login(boolean quiet) {
- try {
- String token = client.login(getUsername(), getPassword());
- log.info("Connected to " + getUrl() + " as " + token);
- return token;
- }
- catch (Exception ex) {
- if (!quiet) {
- log.error("I was unable to login", ex);
- }
-
- return null;
- }
- }
-
- private BugzillaClient client() throws IOException {
-
- if (client == null) {
- try {
- log.debug("Connecting to bugzilla at URL " + getUrl());
- client = new BugzillaClient(getUrl());
- log.debug("Autenthicating as user " + getUsername());
- } catch (Exception ex) {
- log.info("Unable to connect to " + getUrl() + " as "
- + getUsername());
- throw new IOException(ex);
- }
-
- login();
- }
-
- return client;
- }
-
- private <P> P execute(Callable<P> function) throws IOException {
-
- int attempt = 0;
- while(true) {
- try {
- return function.call();
- } catch (Exception ex) {
- if (isRecoverable(ex) && ++attempt < MAX_ATTEMPTS) {
- log.debug("Call failed - retrying, attempt {} of {}", attempt, MAX_ATTEMPTS);
- logout(true);
- login(true);
- continue;
- }
-
- if (ex instanceof IOException)
- throw ((IOException)ex);
- else
- throw new IOException(ex);
- }
- }
- }
-
- private boolean isRecoverable(Exception ex) {
- return false;
- }
-
- private String getPassword() {
- final String pass =
- gerritConfig.getString(pluginName, null,
- GERRIT_CONFIG_PASSWORD);
- return pass;
- }
-
- private String getUsername() {
- final String user =
- gerritConfig.getString(pluginName, null,
- GERRIT_CONFIG_USERNAME);
- return user;
- }
-
- private String getUrl() {
- final String url =
- gerritConfig.getString(pluginName, null, GERRIT_CONFIG_URL);
- return url;
- }
-
- @Override
- public String createLinkForWebui(String url, String text) {
- String ret = url;
- if (text != null && ! text.equals(url)) {
- ret += " (" + text + ")";
- }
- return ret;
- }
-
- private String healthCheckAccess() throws BugzillaException, ConnectionException {
- BugzillaClient client = new BugzillaClient(getUrl());
- client.login(getUsername(), getPassword());
- client.logout();
- final String result = "{\"status\"=\"ok\",\"username\"=\""+getUsername()+"\"}";
- log.debug("Healtheck on access result: {}", result);
- return result;
- }
-
- private String healthCheckSysinfo() throws BugzillaException, IOException {
- final String result = "{\"status\"=\"ok\",\"system\"=\"Bugzilla\",\"version\"=\""+client().getServerVersion()+"\",\"url\"=\""+getUrl()+"\"}";
- log.debug("Healtheck on sysinfo result: {}", result);
- return result;
- }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/bz/InitBugzilla.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/bz/InitBugzilla.java
deleted file mode 100644
index e9175da..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/bz/InitBugzilla.java
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright (C) 2013 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.googlesource.gerrit.plugins.hooks.bz;
-
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.pgm.init.api.AllProjectsConfig;
-import com.google.gerrit.pgm.init.api.AllProjectsNameOnInitProvider;
-import com.google.gerrit.pgm.init.api.InitFlags;
-import com.google.gerrit.pgm.init.api.Section;
-import com.google.gerrit.pgm.init.api.ConsoleUI;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-import com.googlesource.gerrit.plugins.hooks.its.InitIts;
-import com.googlesource.gerrit.plugins.hooks.validation.ItsAssociationPolicy;
-import com.j2bugzilla.base.BugzillaException;
-import com.j2bugzilla.base.ConnectionException;
-
-import org.eclipse.jgit.errors.ConfigInvalidException;
-
-import java.io.IOException;
-import java.util.Arrays;
-
-/** Initialize the GitRepositoryManager configuration section. */
-@Singleton
-class InitBugzilla extends InitIts {
- private final String pluginName;
- private final Section.Factory sections;
- private final InitFlags flags;
- private Section bugzilla;
- private Section bugzillaComment;
- private String bugzillaUrl;
- private String bugzillaUsername;
- private String bugzillaPassword;
-
- @Inject
- InitBugzilla(@PluginName String pluginName, ConsoleUI ui,
- Section.Factory sections, AllProjectsConfig allProjectsConfig,
- AllProjectsNameOnInitProvider allProjects, InitFlags flags) {
- super(pluginName, "Bugzilla", ui, allProjectsConfig, allProjects);
- this.pluginName = pluginName;
- this.sections = sections;
- this.flags = flags;
- }
-
- @Override
- public void run() throws IOException, ConfigInvalidException {
- super.run();
-
- ui.message("\n");
- ui.header("Bugzilla connectivity");
-
- if (!pluginName.equalsIgnoreCase("bugzilla")
- && !flags.cfg.getSections().contains(pluginName)
- && flags.cfg.getSections().contains("bugzilla")) {
- ui.message("A Bugzilla configuration for the 'hooks-bugzilla' plugin was found.\n");
- if (ui.yesno(true, "Copy it for the '%s' plugin?", pluginName)) {
- for (String n : flags.cfg.getNames("bugzilla")) {
- flags.cfg.setStringList(pluginName, null, n,
- Arrays.asList(flags.cfg.getStringList("bugzilla", null, n)));
- }
- for (String n : flags.cfg.getNames(COMMENT_LINK_SECTION, "bugzilla")) {
- flags.cfg.setStringList(COMMENT_LINK_SECTION, pluginName, n,
- Arrays.asList(flags.cfg.getStringList(COMMENT_LINK_SECTION, "bugzilla", n)));
- }
-
- if (ui.yesno(false, "Remove configuration for 'hooks-bugzilla' plugin?")) {
- flags.cfg.unsetSection("bugzilla", null);
- flags.cfg.unsetSection(COMMENT_LINK_SECTION, "bugzilla");
- }
- } else {
- init();
- }
- } else {
- init();
- }
- }
-
- private void init() {
- this.bugzilla = sections.get(pluginName, null);
- this.bugzillaComment = sections.get(COMMENT_LINK_SECTION, pluginName);
-
-
- do {
- enterBugzillaConnectivity();
- } while (bugzillaUrl != null
- && (isConnectivityRequested(bugzillaUrl) && !isBugzillaConnectSuccessful()));
-
- if (bugzillaUrl == null) {
- return;
- }
-
- ui.header("Bugzilla issue-tracking association");
- bugzillaComment.string("Bugzilla bug number regex", "match", "\\([Bb][Uu][Gg][ ]*[1-9][0-9]*\\)");
- bugzillaComment.set("html",
- String.format("<a href=\"%s/show_bug.cgi?id=$1\">$1</a>", bugzillaUrl));
- bugzillaComment.select("Bug number enforced in commit message", "association",
- ItsAssociationPolicy.SUGGESTED);
- }
-
- public void enterBugzillaConnectivity() {
- bugzillaUrl = bugzilla.string("Bugzilla URL (empty to skip)", "url", null);
- if (bugzillaUrl != null) {
- bugzillaUsername = bugzilla.string("Bugzilla username", "username", "");
- bugzillaPassword = bugzilla.password("username", "password");
- }
- }
-
- private boolean isBugzillaConnectSuccessful() {
- ui.message("Checking Bugzilla connectivity ... ");
- try {
- BugzillaClient bugzillaClient = new BugzillaClient(bugzillaUrl);
- bugzillaClient.login(bugzillaUsername, bugzillaPassword);
- bugzillaClient.logout();
- ui.message("[OK]\n");
- return true;
- } catch (BugzillaException e) {
- ui.message("*FAILED* (%s)\n", e.toString());
- return false;
- } catch (ConnectionException e) {
- ui.message("*FAILED* (%s)\n", e.toString());
- return false;
- }
- }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/PhabricatorItsFacade.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/PhabricatorItsFacade.java
new file mode 100644
index 0000000..999513b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/PhabricatorItsFacade.java
@@ -0,0 +1,119 @@
+// Copyright (C) 2013 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.googlesource.gerrit.plugins.its.phabricator;
+
+import java.io.IOException;
+import java.net.URL;
+
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+
+import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.Conduit;
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.ConduitErrorException;
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.ConduitException;
+
+public class PhabricatorItsFacade implements ItsFacade {
+ private static final Logger log = LoggerFactory.getLogger(PhabricatorItsFacade.class);
+
+ private static final String GERRIT_CONFIG_URL = "url";
+ private static final String GERRIT_CONFIG_USERNAME = "username";
+ private static final String GERRIT_CONFIG_CERTIFICATE = "certificate";
+
+ private final Conduit conduit;
+
+ @Inject
+ public PhabricatorItsFacade(@PluginName String pluginName,
+ @GerritServerConfig Config cfg) {
+ final String url = cfg.getString(pluginName, null, GERRIT_CONFIG_URL);
+ final String username = cfg.getString(pluginName, null,
+ GERRIT_CONFIG_USERNAME);
+ final String certificate = cfg.getString(pluginName, null,
+ GERRIT_CONFIG_CERTIFICATE);
+
+ this.conduit = new Conduit(url, username, certificate);
+ }
+
+ @Override
+ public void addComment(final String bugId, final String comment)
+ throws IOException {
+ int task_id = Integer.parseInt(bugId);
+ try {
+ conduit.maniphestUpdate(task_id, comment);
+ } catch (ConduitException e) {
+ throw new IOException("Could not update message for task " + task_id, e);
+ }
+ log.debug("Added comment " + comment + " to bug " + task_id);
+ }
+
+ @Override
+ public void addRelatedLink(final String issueKey, final URL relatedUrl,
+ String description) throws IOException {
+ addComment(issueKey, "Related URL: " + createLinkForWebui(
+ relatedUrl.toExternalForm(), description));
+ }
+
+ @Override
+ public boolean exists(final String bugId) throws IOException {
+ Boolean ret = false;
+ int task_id = Integer.parseInt(bugId);
+ try {
+ try {
+ conduit.maniphestInfo(task_id);
+ ret = true;
+ } catch (ConduitErrorException e) {
+ // An ERR_BAD_TASK just means that the task does not exist.
+ // So the default value of ret would be ok
+ if (! ("ERR_BAD_TASK".equals(e.getErrorCode()))) {
+ // So we had an exception that is /not/ ERR_BAD_TASK.
+ // We have to relay that to the caller.
+ throw e;
+ }
+ }
+ } catch (ConduitException e) {
+ throw new IOException("Could not check existence of task " + task_id, e);
+ }
+ return ret;
+ }
+
+ @Override
+ public void performAction(final String bugId, final String actionString) {
+ // No custom actions at this point.
+ //
+ // Note that you can use hashtag names in comments to associate a task
+ // with a new project.
+ }
+
+ @Override
+ public String healthCheck(final Check check) throws IOException {
+ // This method is not used, so there is no need to implement it.
+ return "unknown";
+ }
+
+ @Override
+ public String createLinkForWebui(String url, String text) {
+ String ret = "[[" + url;
+ if (text != null && !text.isEmpty() && !text.equals(url)) {
+ ret += "|" + text;
+ }
+ ret += "]]";
+ return ret;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaModule.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/PhabricatorModule.java
similarity index 79%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaModule.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/phabricator/PhabricatorModule.java
index 60b5181..421fe21 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/PhabricatorModule.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.googlesource.gerrit.plugins.hooks.bz;
+package com.googlesource.gerrit.plugins.its.phabricator;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
@@ -27,16 +27,16 @@
import com.googlesource.gerrit.plugins.hooks.ItsHookModule;
import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-public class BugzillaModule extends AbstractModule {
+public class PhabricatorModule extends AbstractModule {
- private static final Logger log = LoggerFactory.getLogger(BugzillaModule.class);
+ private static final Logger log = LoggerFactory.getLogger(PhabricatorModule.class);
private final String pluginName;
private final Config gerritConfig;
private final PluginConfigFactory pluginCfgFactory;
@Inject
- public BugzillaModule(@PluginName final String pluginName,
+ public PhabricatorModule(@PluginName final String pluginName,
@GerritServerConfig final Config config,
PluginConfigFactory pluginCfgFactory) {
this.pluginName = pluginName;
@@ -47,8 +47,8 @@
@Override
protected void configure() {
if (gerritConfig.getString(pluginName, null, "url") != null) {
- log.info("Bugzilla is configured as ITS");
- bind(ItsFacade.class).toInstance(new BugzillaItsFacade(pluginName, gerritConfig));
+ log.info("Phabricator is configured as ITS");
+ bind(ItsFacade.class).toInstance(new PhabricatorItsFacade(pluginName, gerritConfig));
install(new ItsHookModule(pluginName, pluginCfgFactory));
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/Conduit.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/Conduit.java
new file mode 100644
index 0000000..fe42b5b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/Conduit.java
@@ -0,0 +1,185 @@
+//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.googlesource.gerrit.plugins.its.phabricator.conduit;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ConduitConnect;
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ConduitPing;
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ManiphestInfo;
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ManiphestUpdate;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.bind.DatatypeConverter;
+
+
+/**
+ * Bindings for Phabricator's Conduit API
+ * <p/>
+ * This class is not thread-safe.
+ */
+public class Conduit {
+
+ private static final Logger log = LoggerFactory.getLogger(Conduit.class);
+
+ public static final int CONDUIT_VERSION = 6;
+
+ private final ConduitConnection conduitConnection;
+ private final Gson gson;
+
+ private String username;
+ private String certificate;
+
+ private String sessionKey;
+
+ public Conduit(final String baseUrl) {
+ this(baseUrl, null, null);
+ }
+
+ public Conduit(final String baseUrl, final String username, final String certificate) {
+ this.conduitConnection = new ConduitConnection(baseUrl);
+ this.username = username;
+ this.certificate = certificate;
+ this.gson = new Gson();
+ resetSession();
+ }
+
+ private void resetSession() {
+ sessionKey = null;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ resetSession();
+ }
+
+ public void setCertificate(String certificate) {
+ this.certificate = certificate;
+ resetSession();
+ }
+
+ /**
+ * Adds session parameters to a Map of parameters
+ * <p/>
+ * If there is no active session, a new one is opened
+ * <p/>
+ * This method overrides the params' __conduit__ value.
+ *
+ * @param params The Map to add session paramaters to
+ */
+ private void fillInSession(Map<String, Object> params) throws ConduitException {
+ if (sessionKey == null) {
+ log.debug("Trying to start new session");
+ conduitConnect();
+ }
+ Map<String, Object> conduitParams = new HashMap<String, Object>();
+ conduitParams.put("sessionKey",sessionKey);
+ params.put("__conduit__", conduitParams);
+ }
+
+ /**
+ * Runs the API's 'conduit.ping' method
+ */
+ public ConduitPing conduitPing() throws ConduitException {
+ JsonElement callResult = conduitConnection.call("conduit.ping");
+ JsonObject callResultWrapper = new JsonObject();
+ callResultWrapper.add("hostname", callResult);
+ ConduitPing result = gson.fromJson(callResultWrapper, ConduitPing.class);
+ return result;
+ }
+
+ /**
+ * Runs the API's 'conduit.connect' method
+ */
+ public ConduitConnect conduitConnect() throws ConduitException {
+ Map<String, Object> params = new HashMap<String, Object>();
+ params.put("client", "its-phabricator");
+ params.put("clientVersion", CONDUIT_VERSION);
+ params.put("user", username);
+
+ // According to phabricator/src/applications/conduit/method/ConduitConnectConduitAPIMethod.php,
+ // the authToken needs to be an integer that is within 15 minutes of the
+ // server's current timestamp.
+ long authToken = System.currentTimeMillis() / 1000;
+ params.put("authToken", authToken);
+
+ // According to phabricator/src/applications/conduit/method/ConduitConnectConduitAPIMethod.php,
+ // The signature is the SHA1 of the concatenation of the authToken (as
+ // string) and the certificate (The long sequence of digits and lowercase
+ // hat get written into ~/.arcrc after "arc install-certificate").
+ String authSignatureInput = Long.toString(authToken) + certificate;
+
+ MessageDigest sha1;
+ try {
+ sha1 = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ throw new ConduitException("Failed to compute authSignature, as no "
+ + "SHA-1 algorithm implementation was found", e);
+ }
+ byte[] authSignatureRaw;
+ try {
+ authSignatureRaw = sha1.digest(authSignatureInput.getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new ConduitException("Failed to convert authSignature input to "
+ + "UTF-8 String", e);
+ }
+ String authSignatureUC = DatatypeConverter.printHexBinary(authSignatureRaw);
+ String authSignature = authSignatureUC.toLowerCase();
+ params.put("authSignature", authSignature);
+
+ JsonElement callResult = conduitConnection.call("conduit.connect", params);
+
+ ConduitConnect result = gson.fromJson(callResult, ConduitConnect.class);
+ sessionKey = result.getSessionKey();
+ return result;
+ }
+
+ /**
+ * Runs the API's 'maniphest.Info' method
+ */
+ public ManiphestInfo maniphestInfo(int taskId) throws ConduitException {
+ Map<String, Object> params = new HashMap<String, Object>();
+ fillInSession(params);
+ params.put("task_id", taskId);
+
+ JsonElement callResult = conduitConnection.call("maniphest.info", params);
+ ManiphestInfo result = gson.fromJson(callResult, ManiphestInfo.class);
+ return result;
+ }
+
+ /**
+ * Runs the API's 'maniphest.update' method
+ */
+ public ManiphestUpdate maniphestUpdate(int taskId, String comment) throws ConduitException {
+ Map<String, Object> params = new HashMap<String, Object>();
+ fillInSession(params);
+ params.put("id", taskId);
+ params.put("comments", comment);
+
+ JsonElement callResult = conduitConnection.call("maniphest.update", params);
+ ManiphestUpdate result = gson.fromJson(callResult, ManiphestUpdate.class);
+ return result;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitConnection.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitConnection.java
new file mode 100644
index 0000000..72d343c
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitConnection.java
@@ -0,0 +1,140 @@
+//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.googlesource.gerrit.plugins.its.phabricator.conduit;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.CallCapsule;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Abstracts the connection to Conduit API
+ */
+class ConduitConnection {
+ private static final Logger log = LoggerFactory.getLogger(Conduit.class);
+
+ private final String apiUrlBase;
+ private final Gson gson;
+
+ private CloseableHttpClient client;
+
+ ConduitConnection(final String baseUrl) {
+ apiUrlBase = baseUrl.replaceAll("/+$", "") + "/api/";
+ gson = new Gson();
+ client = null;
+ }
+
+ /**
+ * Gives a cached HttpClient
+ * <p/>
+ * If no cached HttpClient exists, a new one is spawned.
+ *
+ * @return the cached CloseableHttpClient
+ */
+ private CloseableHttpClient getClient() {
+ if (client == null) {
+ log.trace("Creating new client connection");
+ client = HttpClients.createDefault();
+ }
+ return client;
+ }
+
+ /**
+ * Call the given Conduit method without parameters
+ *
+ * @param method The name of the method that should get called
+ * @return The call's result, if there has been no error
+ * @throws Exception
+ */
+ JsonElement call(String method) throws ConduitException {
+ return call(method, new HashMap<String, Object>());
+ }
+
+ /**
+ * Calls a conduit method with some parameters
+ *
+ * @param method The name of the method that should get called
+ * @param params A map of parameters to pass to the call
+ * @return The call's result, if there has been no error
+ * @throws Exception
+ */
+ JsonElement call(String method, Map<String, Object> params) throws ConduitException {
+ String methodUrl = apiUrlBase + method;
+
+ HttpPost httppost = new HttpPost(methodUrl);
+
+
+ String json = gson.toJson(params);
+
+ log.trace("Calling phabricator method " + method
+ + " with the parameters " + json );
+ try {
+ httppost.setEntity(new StringEntity("params=" + json));
+ } catch (UnsupportedEncodingException e) {
+ throw new ConduitException("Cannot encode parameters", e);
+ }
+
+ CloseableHttpResponse response;
+ try {
+ response = getClient().execute(httppost);
+ } catch (IOException e) {
+ throw new ConduitException("Could not execute Phabricator API call", e);
+ }
+ try {
+ log.trace("Phabricator HTTP response status: " + response.getStatusLine());
+ HttpEntity entity = response.getEntity();
+ String entityString;
+ try {
+ entityString = EntityUtils.toString(entity);
+ } catch (IOException e) {
+ throw new ConduitException("Could not read the API response", e);
+ }
+
+ log.trace("Phabricator response " + entityString);
+ CallCapsule callCapsule = gson.fromJson(entityString, CallCapsule.class);
+ log.trace("callCapsule.result: " + callCapsule.getResult());
+ log.trace("callCapsule.error_code: " + callCapsule.getErrorCode());
+ log.trace("callCapsule.error_info: " + callCapsule.getErrorInfo());
+ if (callCapsule.getErrorCode() != null
+ || callCapsule.getErrorInfo() != null) {
+ throw new ConduitErrorException(method, callCapsule.getErrorCode(),
+ callCapsule.getErrorInfo());
+ }
+ return callCapsule.getResult();
+ } finally {
+ try {
+ response.close();
+ } catch (IOException e) {
+ throw new ConduitException("Could not close API response", e);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitErrorException.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitErrorException.java
new file mode 100644
index 0000000..c684297
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitErrorException.java
@@ -0,0 +1,44 @@
+//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.googlesource.gerrit.plugins.its.phabricator.conduit;
+
+/**
+ * Exception thrown if Conduit response has error_code or error_info set
+ */
+public class ConduitErrorException extends ConduitException {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String errorCode;
+ private final String errorInfo;
+
+ ConduitErrorException(String method, String errorCode, String errorInfo) {
+ super("Method '" + method + "' gave: " +errorCode +
+ ((errorInfo != null && !errorInfo.isEmpty())
+ ? (", " + errorInfo)
+ : ""));
+ this.errorCode = errorCode;
+ this.errorInfo = errorInfo;
+ }
+
+ public String getErrorCode() {
+ return errorCode;
+ }
+
+ public String getErrorInfo() {
+ return errorInfo;
+ }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitException.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitException.java
new file mode 100644
index 0000000..edde2fd
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitException.java
@@ -0,0 +1,35 @@
+//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.googlesource.gerrit.plugins.its.phabricator.conduit;
+
+/**
+ * Generic wrapper for Exceptions caused by processing for Conduit
+ */
+public class ConduitException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public ConduitException() {
+ super();
+ }
+
+ public ConduitException(String message) {
+ super(message);
+ }
+
+ public ConduitException(String message, Throwable e) {
+ super(message, e);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/CallCapsule.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/CallCapsule.java
new file mode 100644
index 0000000..dcf0484
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/CallCapsule.java
@@ -0,0 +1,46 @@
+//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.googlesource.gerrit.plugins.its.phabricator.conduit.results;
+
+import com.google.gson.JsonElement;
+
+/**
+ * Models the generic wrapper for API calls
+ * <p/>
+ * JSON looks like:
+ * <pre>
+ * {
+ * "result": SOME JSON OBJECT,
+ * "error_code":null,
+ * "error_info":null
+ * }
+ * </pre>
+ */
+public class CallCapsule {
+ private JsonElement result;
+ private String error_code;
+ private String error_info;
+
+ public JsonElement getResult() {
+ return result;
+ }
+
+ public String getErrorCode() {
+ return error_code;
+ }
+
+ public String getErrorInfo() {
+ return error_info;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ConduitConnect.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ConduitConnect.java
new file mode 100644
index 0000000..52919f5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ConduitConnect.java
@@ -0,0 +1,46 @@
+//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.googlesource.gerrit.plugins.its.phabricator.conduit.results;
+
+/**
+ * Models the result for a call to conduit.connect
+ * <p/>
+ * JSON looks like:
+ * <pre>
+ * {
+ * "connectionID":4,
+ * "sessionKey":"5jhpmsb3xgm1eupp7snzym7mtebndd7v4vv4ub6n",
+ * "userPHID":"PHID-USER-h4n52fq2kt2v3a2qjyqh"
+ * }
+ * </pre>
+ * @author christian
+ *
+ */
+public class ConduitConnect {
+ private int connectionID;
+ private String sessionKey;
+ private String userPHID;
+
+ public int getConnectionId() {
+ return connectionID;
+ }
+
+ public String getSessionKey() {
+ return sessionKey;
+ }
+
+ public String getUserPhId() {
+ return userPHID;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ConduitPing.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ConduitPing.java
new file mode 100644
index 0000000..5d409b3
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ConduitPing.java
@@ -0,0 +1,28 @@
+//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.googlesource.gerrit.plugins.its.phabricator.conduit.results;
+
+/**
+ * Models the result for a call to conduit.ping
+ * <p/>
+ * JSON is just the hostname of the instance. We wrap it in a proper object
+ * to make it a nicer Java citizen.
+ */
+public class ConduitPing {
+ private String hostname;
+
+ public String getHostname() {
+ return hostname;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ManiphestInfo.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ManiphestInfo.java
new file mode 100644
index 0000000..472853e
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ManiphestInfo.java
@@ -0,0 +1,51 @@
+//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.googlesource.gerrit.plugins.its.phabricator.conduit.results;
+
+/**
+* Models the result for a call to maniphest.info
+* <p/>
+* JSON looks like:
+* <pre>
+* {
+* "id":"48",
+* "phid":"PHID-TASK-pemd324eosnymq3tdkyo",
+* "authorPHID":"PHID-USER-na3one2sht11aone",
+* "ownerPHID":null,
+* "ccPHIDs":[
+* "PHID-USER-h4n62fq2kt2v3a2qjyqh"
+* ],
+* "status":"open",
+* "statusName":"Open",
+* "isClosed":false,
+* "priority": "Needs Triage",
+* "priorityColor":"violet",
+* "title":"QChris test task",
+* "description":"",
+* "projectPHIDs":[],
+* "uri":"https://phab-01.wmflabs.org/T47",
+* "auxiliary":{
+* "std:maniphest:security_topic":"default",
+* "isdc:sprint:storypoints":null
+* },
+* "objectName":"T47",
+* "dateCreated":"1413484594",
+* "dateModified":1413549869,
+* "dependsOnTaskPHIDs":[]
+* }
+* </pre>
+*/
+public class ManiphestInfo extends Task {
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ManiphestUpdate.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ManiphestUpdate.java
new file mode 100644
index 0000000..055e8db
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ManiphestUpdate.java
@@ -0,0 +1,51 @@
+//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.googlesource.gerrit.plugins.its.phabricator.conduit.results;
+
+/**
+ * Models the result for a call to maniphest.update
+ * <p/>
+ * JSON looks like:
+ * <pre>
+ * {
+ * "id":"48",
+ * "phid":"PHID-TASK-pemd324eosnymq3tdkyo",
+ * "authorPHID":"PHID-USER-na3one2sht11aone",
+ * "ownerPHID":null,
+ * "ccPHIDs":[
+ * "PHID-USER-h4n62fq2kt2v3a2qjyqh"
+ * ],
+ * "status":"open",
+ * "statusName":"Open",
+ * "isClosed":false,
+ * "priority": "Needs Triage",
+ * "priorityColor":"violet",
+ * "title":"QChris test task",
+ * "description":"",
+ * "projectPHIDs":[],
+ * "uri":"https://phab-01.wmflabs.org/T47",
+ * "auxiliary":{
+ * "std:maniphest:security_topic":"default",
+ * "isdc:sprint:storypoints":null
+ * },
+ * "objectName":"T47",
+ * "dateCreated":"1413484594",
+ * "dateModified":1413549869,
+ * "dependsOnTaskPHIDs":[]
+ * }
+ * </pre>
+ */
+public class ManiphestUpdate extends Task {
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/Task.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/Task.java
new file mode 100644
index 0000000..f6e72b6
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/Task.java
@@ -0,0 +1,147 @@
+//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.googlesource.gerrit.plugins.its.phabricator.conduit.results;
+
+import com.google.gson.JsonElement;
+
+/**
+ * Models the result for API methods returning Task information
+ * <p/>
+ * JSON looks like:
+ * <pre>
+ * {
+ * "id":"48",
+ * "phid":"PHID-TASK-pemd324eosnymq3tdkyo",
+ * "authorPHID":"PHID-USER-na3one2sht11aone",
+ * "ownerPHID":null,
+ * "ccPHIDs":[
+ * "PHID-USER-h4n62fq2kt2v3a2qjyqh"
+ * ],
+ * "status":"open",
+ * "statusName":"Open",
+ * "isClosed":false,
+ * "priority": "Needs Triage",
+ * "priorityColor":"violet",
+ * "title":"QChris test task",
+ * "description":"",
+ * "projectPHIDs":[],
+ * "uri":"https://phab-01.wmflabs.org/T47",
+ * "auxiliary":{
+ * "std:maniphest:security_topic":"default",
+ * "isdc:sprint:storypoints":null
+ * },
+ * "objectName":"T47",
+ * "dateCreated":"1413484594",
+ * "dateModified":1413549869,
+ * "dependsOnTaskPHIDs":[]
+ * }
+ * </pre>
+ */
+public class Task {
+ private int id;
+ private String phid;
+ private String authorPHID;
+ private String ownerPHID;
+ private JsonElement ccPHIDs;
+ private String status;
+ private String statusName;
+ private Boolean isClosed;
+ private String priority;
+ private String priorityColor;
+ private String title;
+ private String description;
+ private JsonElement projectPHIDs;
+ private String uri;
+ private JsonElement auxiliary;
+ private String objectName;
+ private String dateCreated;
+ private String dateModified;
+ private JsonElement dependsOnTaskPHIDs;
+
+ public int getId() {
+ return id;
+ }
+
+ public String getPhid() {
+ return phid;
+ }
+
+ public String getAuthorPHID() {
+ return authorPHID;
+ }
+
+ public String getOwnerPHID() {
+ return ownerPHID;
+ }
+
+ public JsonElement getCcPHIDs() {
+ return ccPHIDs;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public String getStatusName() {
+ return statusName;
+ }
+
+ public Boolean getIsClosed() {
+ return isClosed;
+ }
+
+ public String getPriority() {
+ return priority;
+ }
+
+ public String getPriorityColor() {
+ return priorityColor;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public JsonElement getProjectPHIDs() {
+ return projectPHIDs;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public JsonElement getAuxiliary() {
+ return auxiliary;
+ }
+
+ public String getObjectName() {
+ return objectName;
+ }
+
+ public String getDateCreated() {
+ return dateCreated;
+ }
+
+ public String getDateModified() {
+ return dateModified;
+ }
+
+ public JsonElement getDependsOnTaskPHIDs() {
+ return dependsOnTaskPHIDs;
+ }
+}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index aa749b7..8e578c7 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -1,5 +1,5 @@
`@PLUGIN@` is an [`its-base`][its-base] based plugin that (based on
-events in Gerrit) allows to take actions in Bugzilla. For example, it
-can add comments to bugs, or change status of bugs.
+events in Gerrit) allows to take actions in Phabricator. For example, it
+can add comments to bugs.
-[its-base]: https://gerrit-review.googlesource.com/#/admin/projects/plugins/its-base
\ No newline at end of file
+[its-base]: https://gerrit-review.googlesource.com/#/admin/projects/plugins/its-base
diff --git a/src/main/resources/Documentation/config-connectivity.md b/src/main/resources/Documentation/config-connectivity.md
index 1443f3b..a354c44 100644
--- a/src/main/resources/Documentation/config-connectivity.md
+++ b/src/main/resources/Documentation/config-connectivity.md
@@ -1,63 +1,36 @@
-Bugzilla connectivity
-=====================
+Phabricator connectivity
+========================
-In order for Gerrit to connect to Bugzilla,
-
-1. [make sure that your Bugzilla instance has the XML-RPC interface
- enabled][rpc-enabled], and
-2. [provide url, user and password to @PLUGIN@][gerrit-configuration].
+In order for Gerrit to connect to Bugzilla, provide url, user, and certificate
+to @PLUGIN@.
-[rpc-enabled]: #rpc-enabled
-<a name="rpc-enabled">Checking XML-RPC availability</a>
--------------------------------------------------------
-
-Assuming the Bugzilla instance you want to connect to is at
-`http://my.bugzilla.instance.example.org/`, open
-
-```
-http://my.bugzilla.instance.example.org/xmlrpc.cgi
-```
-
-in your browser. If you get an empty page without errors, the XML-RPC
-interface is enabled. You can continue by [providing the needed Gerrit
-configuration][gerrit-configuration].
-
-If you get an error page saying
-
-```
-The XML-RPC Interface feature is not available in this Bugzilla.
-```
-
-the XML-RPC interface needs to be enabled. To do so, log in to the
-server that's running your Bugzilla instance, go to Bugzilla's
-directory, run
-
-```
-./checksetup.pl --check-modules
-```
-
-and install the missing modules. Then re-check the XML-RPC interface
-availability as above.
-
-[gerrit-configuration]: #gerrit-configuration
-<a name="gerrit-configuration">Gerrit configuration</a>
--------------------------------------------------------
-
-In order for @PLUGIN@ to connect to the XML-RPC service of your
-Bugzilla instance, url (without trailing "/xmlrpc.cgi") and
-credentials are required in your site's `etc/gerrit.config` or
-`etc/secure.config` under the `@PLUGIN@` section.
+In order for @PLUGIN@ to connect to your Phabricator instance, url (without
+trailing “/api”, “/conduit” or some such), user, and certificate are required in
+your site's `etc/gerrit.config` or `etc/secure.config` under the `@PLUGIN@`
+section.
Example:
```
[@PLUGIN@]
- url=http://my.bugzilla.instance.example.org
+ url=http://my.phabricator.instance.example.org
username=USERNAME_TO_CONNECT_TO_BUGZILLA
- password=PASSWORD_FOR_ABOVE_USERNAME
+ certificate=CERTIFICATE_FOR_ABOVE_USERNAME
```
+Note that the certificate is not the user's password. It is … well … the users
+certificate … which is a 255 character long sequence of lowercase letters and
+digits. To get the certificate, run
+
+```
+arc install-certificate http://my.phabricator.instance.example.org
+```
+
+And follow the instructions on the screen. Once the procedure of logging in to
+the Phabricator instance in your browser and copying tokens around is complete,
+you'll find the certificate in `~/.arcrc`.
+
[Back to @PLUGIN@ documentation index][index]
[index]: index.html
diff --git a/src/main/resources/Documentation/config-rulebase-plugin-actions.md b/src/main/resources/Documentation/config-rulebase-plugin-actions.md
deleted file mode 100644
index b3e64be..0000000
--- a/src/main/resources/Documentation/config-rulebase-plugin-actions.md
+++ /dev/null
@@ -1,73 +0,0 @@
-@PLUGIN@-specific actions
-=========================
-
-In addition to the [basic actions][basic-actions], @PLUGIN@ also
-provides:
-
-[`set-resolution`][action-set-resolution]
-: sets the resolution of the issue
-
-[`set-status`][action-set-status]
-: sets the status of the issue
-
-[`set-status-and-resolution`][action-set-status-and-resolution]
-: sets both the status and resolution of the issue
-
-[basic-actions]: config-rulebase-common.html#actions
-
-[action-set-resolution]: #action-set-resolution
-### <a name="action-set-resolution">Action: set-resolution</a>
-
-The `set-resolution` action sets the issue's resolution. The first
-parameter is the resolution to set. So for example
-
-```
- action = set-resolution WORKSFORME
-```
-
-sets the issue's status to `WORKSFORME`.
-
-If you want to set the status and the resolution, use the
-`set-status-and-resolution` action, so you can set both status and
-resolution in one go.
-
-
-
-[action-set-status]: #action-set-status
-### <a name="action-set-status">Action: set-status</a>
-
-The `set-status` action sets the issue's status. The first parameter
-is the status to set. So for example
-
-```
- action = set-status CONFIRMED
-```
-
-sets the issue's status to `CONFIRMED`.
-
-If you want to set the status to a value that also requires a
-resolution, use the `set-status-and-resolution` action, so you can set
-both status and resolution in one go.
-
-
-
-[action-set-status-and-resolution]: #action-set-status-and-resolution
-### <a name="action-set-status-and-resolution">Action: set-status-and-resolution</a>
-
-The `set-status-and-resolution` action sets both the issue's status
-and it's resolution in one go. The first parameter denotes the status
-to set, the second parameter denotes the resolution to set.
-
-So for example
-
-```
- action = set-status-and-resolution RESOLVED FIXED
-```
-
-sets the issue's status to `RESOLVED` and it's resolution to `FIXED`.
-
-
-
-[Back to @PLUGIN@ documentation index][index]
-
-[index]: index.html
\ No newline at end of file
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaItsFacadeTest.java b/src/test/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaItsFacadeTest.java
deleted file mode 100644
index 5c43601..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaItsFacadeTest.java
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright (C) 2013 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.googlesource.gerrit.plugins.hooks.bz;
-
-import static org.easymock.EasyMock.expect;
-
-import org.eclipse.jgit.lib.Config;
-
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.server.config.FactoryModule;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
-
-public class BugzillaItsFacadeTest extends LoggingMockingTestCase {
- private Injector injector;
- private Config serverConfig;
-
- public void testCreateLinkForWebUiPlain() {
- mockUnconnectableBugzilla();
-
- replayMocks();
-
- BugzillaItsFacade itsFacade = createBugzillaItsFacade();
- String actual = itsFacade.createLinkForWebui("Test-Url", "Test-Text");
-
- assertNotNull("Created link is null", actual);
- assertTrue("Created link does not contain url",
- actual.contains("Test-Url"));
- assertTrue("Created link does not contain text",
- actual.contains("Test-Text"));
-
- assertUnconnectableBugzilla();
- }
-
- public void testCreateLinkForWebUiUrlEqualsText() {
- mockUnconnectableBugzilla();
-
- replayMocks();
-
- BugzillaItsFacade itsFacade = createBugzillaItsFacade();
- String actual = itsFacade.createLinkForWebui("Test-Url", "Test-Url");
-
- assertNotNull("Created link is null", actual);
- assertEquals("Created link does not match", "Test-Url", actual);
-
- assertUnconnectableBugzilla();
- }
-
- public void testCreateLinkForWebUiUrlEqualsNull() {
- mockUnconnectableBugzilla();
-
- replayMocks();
-
- BugzillaItsFacade itsFacade = createBugzillaItsFacade();
- String actual = itsFacade.createLinkForWebui("Test-Url", null);
-
- assertNotNull("Created link is null", actual);
- assertEquals("Created link does not match", "Test-Url", actual);
-
- assertUnconnectableBugzilla();
- }
-
- private BugzillaItsFacade createBugzillaItsFacade() {
- return injector.getInstance(BugzillaItsFacade.class);
- }
-
- private void mockUnconnectableBugzilla() {
- expect(serverConfig.getString("its-bugzilla", null, "url"))
- .andReturn("<no-url>").anyTimes();
- expect(serverConfig.getString("its-bugzilla", null, "username"))
- .andReturn("none").anyTimes();
- expect(serverConfig.getString("its-bugzilla", null, "password"))
- .andReturn("none").anyTimes();
- }
-
- private void assertUnconnectableBugzilla() {
- assertLogMessageContains("Connecting to bugzilla");
- assertLogMessageContains("Unable to connect");
- assertLogMessageContains("Bugzilla is currently not available");
- }
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
- injector = Guice.createInjector(new TestModule());
- }
-
- private class TestModule extends FactoryModule {
- @Override
- protected void configure() {
- serverConfig = createMock(Config.class);
- bind(Config.class).annotatedWith(GerritServerConfig.class)
- .toInstance(serverConfig);
- bind(String.class).annotatedWith(PluginName.class)
- .toInstance("its-bugzilla");
- }
- }
-}
\ No newline at end of file
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/phabricator/PhabricatorItsFacadeTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/phabricator/PhabricatorItsFacadeTest.java
new file mode 100644
index 0000000..9b22039
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/phabricator/PhabricatorItsFacadeTest.java
@@ -0,0 +1,105 @@
+// 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.googlesource.gerrit.plugins.its.phabricator;
+
+import static org.easymock.EasyMock.expect;
+
+import org.eclipse.jgit.lib.Config;
+
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.googlesource.gerrit.plugins.its.testutil.LoggingMockingTestCase;
+
+public class PhabricatorItsFacadeTest extends LoggingMockingTestCase {
+ private Injector injector;
+ private Config serverConfig;
+
+ public void testCreateLinkForWebUiDifferentUrlAndText() {
+ mockUnconnectablePhabricator();
+
+ replayMocks();
+
+ PhabricatorItsFacade itsFacade = createPhabricatorItsFacade();
+ String actual = itsFacade.createLinkForWebui("Test-Url", "Test-Text");
+
+ assertEquals("[[Test-Url|Test-Text]]", actual);
+ }
+
+ public void testCreateLinkForWebUiSameUrlAndText() {
+ mockUnconnectablePhabricator();
+
+ replayMocks();
+
+ PhabricatorItsFacade itsFacade = createPhabricatorItsFacade();
+ String actual = itsFacade.createLinkForWebui("Test-Url", "Test-Url");
+
+ assertEquals("[[Test-Url]]", actual);
+ }
+
+ public void testCreateLinkForWebUiNullText() {
+ mockUnconnectablePhabricator();
+
+ replayMocks();
+
+ PhabricatorItsFacade itsFacade = createPhabricatorItsFacade();
+ String actual = itsFacade.createLinkForWebui("Test-Url", null);
+
+ assertEquals("[[Test-Url]]", actual);
+ }
+
+ public void testCreateLinkForWebUiEmptyText() {
+ mockUnconnectablePhabricator();
+
+ replayMocks();
+
+ PhabricatorItsFacade itsFacade = createPhabricatorItsFacade();
+ String actual = itsFacade.createLinkForWebui("Test-Url", "");
+
+ assertEquals("[[Test-Url]]", actual);
+ }
+
+ private PhabricatorItsFacade createPhabricatorItsFacade() {
+ return injector.getInstance(PhabricatorItsFacade.class);
+ }
+
+ private void mockUnconnectablePhabricator() {
+ expect(serverConfig.getString("its-phabricator", null, "url"))
+ .andReturn("<no-url>").anyTimes();
+ expect(serverConfig.getString("its-phabricator", null, "username"))
+ .andReturn("none").anyTimes();
+ expect(serverConfig.getString("its-phabricator", null, "certificate"))
+ .andReturn("none").anyTimes();
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ injector = Guice.createInjector(new TestModule());
+ }
+
+ private class TestModule extends FactoryModule {
+ @Override
+ protected void configure() {
+ serverConfig = createMock(Config.class);
+ bind(Config.class).annotatedWith(GerritServerConfig.class)
+ .toInstance(serverConfig);
+ bind(String.class).annotatedWith(PluginName.class)
+ .toInstance("its-phabricator");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitTest.java
new file mode 100644
index 0000000..769d7fb
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitTest.java
@@ -0,0 +1,402 @@
+//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.googlesource.gerrit.plugins.its.phabricator.conduit;
+
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.resetToStrict;
+import static org.powermock.api.easymock.PowerMock.expectNew;
+
+import org.easymock.Capture;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.Map;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ConduitConnect;
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ConduitPing;
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ManiphestInfo;
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ManiphestUpdate;
+import com.googlesource.gerrit.plugins.its.testutil.LoggingMockingTestCase;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(Conduit.class)
+public class ConduitTest extends LoggingMockingTestCase {
+ private final static String URL = "urlFoo";
+ private final static String USERNAME = "usernameFoo";
+ private final static String CERTIFICATE = "certificateFoo";
+
+ private ConduitConnection connection;
+
+ public void testConduitPingPass() throws Exception {
+ mockConnection();
+
+ expect(connection.call("conduit.ping"))
+ .andReturn(new JsonPrimitive("foo"))
+ .once();
+
+ replayMocks();
+
+ Conduit conduit = new Conduit(URL);
+
+ ConduitPing actual = conduit.conduitPing();
+
+ assertEquals("Hostname does not match", "foo", actual.getHostname());
+ }
+
+ public void testConduitPingConnectionFail() throws Exception {
+ mockConnection();
+
+ ConduitException conduitException = new ConduitException();
+
+ expect(connection.call("conduit.ping"))
+ .andThrow(conduitException)
+ .once();
+
+ replayMocks();
+
+ Conduit conduit = new Conduit(URL);
+
+ try {
+ conduit.conduitPing();
+ fail("no exception got thrown");
+ } catch (ConduitException e) {
+ assertSame(conduitException, e);
+ }
+ }
+
+ public void testConduitConnectPass() throws Exception {
+ mockConnection();
+
+ JsonObject ret = new JsonObject();
+ ret.add("sessionKey", new JsonPrimitive("KeyFoo"));
+
+ Capture<Map<String, Object>> paramsCapture = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("conduit.connect"), capture(paramsCapture)))
+ .andReturn(ret)
+ .once();
+
+ replayMocks();
+
+ Conduit conduit = new Conduit(URL, USERNAME, CERTIFICATE);
+
+ ConduitConnect conduitConnect = conduit.conduitConnect();
+
+ Map<String, Object> params = paramsCapture.getValue();
+ assertEquals("Usernames do not match", USERNAME, params.get("user"));
+
+ assertEquals("Session keys don't match", "KeyFoo",
+ conduitConnect.getSessionKey());
+ }
+
+ public void testConduitConnectConnectionFail() throws Exception {
+ mockConnection();
+
+ ConduitException conduitException = new ConduitException();
+
+ Capture<Map<String, Object>> paramsCapture = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("conduit.connect"), capture(paramsCapture)))
+ .andThrow(conduitException)
+ .once();
+
+ replayMocks();
+
+ Conduit conduit = new Conduit(URL, USERNAME, CERTIFICATE);
+
+ try {
+ conduit.conduitConnect();
+ fail("no exception got thrown");
+ } catch (ConduitException e) {
+ assertSame(conduitException, e);
+ }
+
+ Map<String, Object> params = paramsCapture.getValue();
+ assertEquals("Usernames do not match", USERNAME, params.get("user"));
+ }
+
+ public void testManiphestInfoPass() throws Exception {
+ mockConnection();
+
+ resetToStrict(connection);
+
+ JsonObject retConnect = new JsonObject();
+ retConnect.add("sessionKey", new JsonPrimitive("KeyFoo"));
+
+ Capture<Map<String, Object>> paramsCaptureConnect = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("conduit.connect"), capture(paramsCaptureConnect)))
+ .andReturn(retConnect)
+ .once();
+
+ JsonObject retRelevant = new JsonObject();
+ retRelevant.add("id", new JsonPrimitive(42));
+
+ Capture<Map<String, Object>> paramsCaptureRelevant = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("maniphest.info"), capture(paramsCaptureRelevant)))
+ .andReturn(retRelevant)
+ .once();
+
+ replayMocks();
+
+ Conduit conduit = new Conduit(URL, USERNAME, CERTIFICATE);
+
+ ManiphestInfo maniphestInfo = conduit.maniphestInfo(42);
+
+ Map<String, Object> paramsConnect = paramsCaptureConnect.getValue();
+ assertEquals("Usernames do not match", USERNAME, paramsConnect.get("user"));
+
+ Map<String, Object> paramsRelevant = paramsCaptureRelevant.getValue();
+ assertEquals("Task id is not set", 42, paramsRelevant.get("task_id"));
+
+ assertEquals("ManiphestInfo's id does not match", 42, maniphestInfo.getId());
+
+ assertLogMessageContains("Trying to start new session");
+ }
+
+ public void testManiphestInfoFailConnect() throws Exception {
+ mockConnection();
+
+ ConduitException conduitException = new ConduitException();
+
+ Capture<Map<String, Object>> paramsCapture = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("conduit.connect"), capture(paramsCapture)))
+ .andThrow(conduitException)
+ .once();
+
+ replayMocks();
+
+ Conduit conduit = new Conduit(URL, USERNAME, CERTIFICATE);
+
+ try {
+ conduit.maniphestInfo(42);
+ fail("no exception got thrown");
+ } catch (ConduitException e) {
+ assertSame(conduitException, e);
+ }
+
+ Map<String, Object> params = paramsCapture.getValue();
+ assertEquals("Usernames do not match", USERNAME, params.get("user"));
+
+ assertLogMessageContains("Trying to start new session");
+ }
+
+ public void testManiphestInfoFailRelevant() throws Exception {
+ mockConnection();
+
+ resetToStrict(connection);
+
+ JsonObject retConnect = new JsonObject();
+ retConnect.add("sessionKey", new JsonPrimitive("KeyFoo"));
+
+ Capture<Map<String, Object>> paramsCaptureConnect = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("conduit.connect"), capture(paramsCaptureConnect)))
+ .andReturn(retConnect)
+ .once();
+
+ ConduitException conduitException = new ConduitException();
+
+ Capture<Map<String, Object>> paramsCaptureRelevant = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("maniphest.info"), capture(paramsCaptureRelevant)))
+ .andThrow(conduitException)
+ .once();
+
+ replayMocks();
+
+ Conduit conduit = new Conduit(URL, USERNAME, CERTIFICATE);
+
+ try {
+ conduit.maniphestInfo(42);
+ fail("no exception got thrown");
+ } catch (ConduitException e) {
+ assertSame(conduitException, e);
+ }
+
+ Map<String, Object> paramsConnect = paramsCaptureConnect.getValue();
+ assertEquals("Usernames do not match", USERNAME, paramsConnect.get("user"));
+
+ Map<String, Object> paramsRelevant = paramsCaptureRelevant.getValue();
+ assertEquals("Task id is not set", 42, paramsRelevant.get("task_id"));
+
+ assertLogMessageContains("Trying to start new session");
+ }
+
+ public void testManiphestUpdatePass() throws Exception {
+ mockConnection();
+
+ resetToStrict(connection);
+
+ JsonObject retConnect = new JsonObject();
+ retConnect.add("sessionKey", new JsonPrimitive("KeyFoo"));
+
+ Capture<Map<String, Object>> paramsCaptureConnect = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("conduit.connect"), capture(paramsCaptureConnect)))
+ .andReturn(retConnect)
+ .once();
+
+ JsonObject retRelevant = new JsonObject();
+ retRelevant.add("id", new JsonPrimitive(42));
+
+ Capture<Map<String, Object>> paramsCaptureRelevant = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("maniphest.update"), capture(paramsCaptureRelevant)))
+ .andReturn(retRelevant)
+ .once();
+
+ replayMocks();
+
+ Conduit conduit = new Conduit(URL, USERNAME, CERTIFICATE);
+
+ ManiphestUpdate maniphestUpdate = conduit.maniphestUpdate(42, "foo");
+
+ Map<String, Object> paramsConnect = paramsCaptureConnect.getValue();
+ assertEquals("Usernames do not match", USERNAME, paramsConnect.get("user"));
+
+ Map<String, Object> paramsRelevant = paramsCaptureRelevant.getValue();
+ assertEquals("Task id is not set", 42, paramsRelevant.get("id"));
+
+ assertEquals("ManiphestInfo's id does not match", 42, maniphestUpdate.getId());
+
+ assertLogMessageContains("Trying to start new session");
+ }
+
+ public void testManiphestUpdateFailConnect() throws Exception {
+ mockConnection();
+
+ ConduitException conduitException = new ConduitException();
+
+ Capture<Map<String, Object>> paramsCapture = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("conduit.connect"), capture(paramsCapture)))
+ .andThrow(conduitException)
+ .once();
+
+ replayMocks();
+
+ Conduit conduit = new Conduit(URL, USERNAME, CERTIFICATE);
+
+ try {
+ conduit.maniphestUpdate(42, "foo");
+ fail("no exception got thrown");
+ } catch (ConduitException e) {
+ assertSame(conduitException, e);
+ }
+
+ Map<String, Object> params = paramsCapture.getValue();
+ assertEquals("Usernames do not match", USERNAME, params.get("user"));
+
+ assertLogMessageContains("Trying to start new session");
+ }
+
+ public void testManiphestUpdateFailRelevant() throws Exception {
+ mockConnection();
+
+ resetToStrict(connection);
+
+ JsonObject retConnect = new JsonObject();
+ retConnect.add("sessionKey", new JsonPrimitive("KeyFoo"));
+
+ Capture<Map<String, Object>> paramsCaptureConnect = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("conduit.connect"), capture(paramsCaptureConnect)))
+ .andReturn(retConnect)
+ .once();
+
+ ConduitException conduitException = new ConduitException();
+
+ Capture<Map<String, Object>> paramsCaptureRelevant = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("maniphest.update"), capture(paramsCaptureRelevant)))
+ .andThrow(conduitException)
+ .once();
+
+ replayMocks();
+
+ Conduit conduit = new Conduit(URL, USERNAME, CERTIFICATE);
+
+ try {
+ conduit.maniphestUpdate(42, "foo");
+ fail("no exception got thrown");
+ } catch (ConduitException e) {
+ assertSame(conduitException, e);
+ }
+
+ Map<String, Object> paramsConnect = paramsCaptureConnect.getValue();
+ assertEquals("Usernames do not match", USERNAME, paramsConnect.get("user"));
+
+ Map<String, Object> paramsRelevant = paramsCaptureRelevant.getValue();
+ assertEquals("Task id is not set", 42, paramsRelevant.get("id"));
+
+ assertLogMessageContains("Trying to start new session");
+ }
+
+ public void testConnectionReuse() throws Exception {
+ mockConnection();
+
+ resetToStrict(connection);
+
+ JsonObject retConnect = new JsonObject();
+ retConnect.add("sessionKey", new JsonPrimitive("KeyFoo"));
+
+ Capture<Map<String, Object>> paramsCaptureConnect = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("conduit.connect"), capture(paramsCaptureConnect)))
+ .andReturn(retConnect)
+ .once();
+
+ JsonObject retRelevant = new JsonObject();
+ retRelevant.add("id", new JsonPrimitive(42));
+
+ Capture<Map<String, Object>> paramsCaptureRelevant = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("maniphest.info"), capture(paramsCaptureRelevant)))
+ .andReturn(retRelevant)
+ .once();
+
+ replayMocks();
+
+ Conduit conduit = new Conduit(URL, USERNAME, CERTIFICATE);
+
+ ConduitConnect conduitConnect = conduit.conduitConnect();
+ ManiphestInfo maniphestInfo = conduit.maniphestInfo(42);
+
+ Map<String, Object> paramsConnect = paramsCaptureConnect.getValue();
+ assertEquals("Usernames do not match", USERNAME, paramsConnect.get("user"));
+
+ Map<String, Object> paramsRelevant = paramsCaptureRelevant.getValue();
+ assertEquals("Task id is not set", 42, paramsRelevant.get("task_id"));
+
+ assertEquals("Session keys don't match", "KeyFoo", conduitConnect.getSessionKey());
+
+ assertEquals("ManiphestInfo's id does not match", 42, maniphestInfo.getId());
+ }
+
+ private void mockConnection() throws Exception {
+ connection = createMock(ConduitConnection.class);;
+ expectNew(ConduitConnection.class, URL)
+ .andReturn(connection)
+ .once();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/LoggingMockingTestCase.java b/src/test/java/com/googlesource/gerrit/plugins/its/testutil/LoggingMockingTestCase.java
similarity index 94%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/LoggingMockingTestCase.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/testutil/LoggingMockingTestCase.java
index 4bc25b8..a2d4d00 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/LoggingMockingTestCase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/testutil/LoggingMockingTestCase.java
@@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.googlesource.gerrit.plugins.hooks.testutil;
+package com.googlesource.gerrit.plugins.its.testutil;
import com.google.common.collect.Lists;
-import com.googlesource.gerrit.plugins.hooks.testutil.MockingTestCase;
-import com.googlesource.gerrit.plugins.hooks.testutil.log.LogUtil;
+import com.googlesource.gerrit.plugins.its.testutil.MockingTestCase;
+import com.googlesource.gerrit.plugins.its.testutil.log.LogUtil;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.After;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/MockingTestCase.java b/src/test/java/com/googlesource/gerrit/plugins/its/testutil/MockingTestCase.java
similarity index 98%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/MockingTestCase.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/testutil/MockingTestCase.java
index 819b138..060269b 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/MockingTestCase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/testutil/MockingTestCase.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.googlesource.gerrit.plugins.hooks.testutil;
+package com.googlesource.gerrit.plugins.its.testutil;
import junit.framework.TestCase;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/CollectionAppender.java b/src/test/java/com/googlesource/gerrit/plugins/its/testutil/log/CollectionAppender.java
similarity index 95%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/CollectionAppender.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/testutil/log/CollectionAppender.java
index f63c7c3..8f21bde 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/CollectionAppender.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/testutil/log/CollectionAppender.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.googlesource.gerrit.plugins.hooks.testutil.log;
+package com.googlesource.gerrit.plugins.its.testutil.log;
import com.google.common.collect.Lists;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/LogUtil.java b/src/test/java/com/googlesource/gerrit/plugins/its/testutil/log/LogUtil.java
similarity index 88%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/LogUtil.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/testutil/log/LogUtil.java
index 8ea559c..4fee52f 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/LogUtil.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/testutil/log/LogUtil.java
@@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.googlesource.gerrit.plugins.hooks.testutil.log;
+package com.googlesource.gerrit.plugins.its.testutil.log;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
-import com.googlesource.gerrit.plugins.hooks.testutil.log.CollectionAppender;
+import com.googlesource.gerrit.plugins.its.testutil.log.CollectionAppender;
import java.util.Collection;