IBM Rational Team Concert integration for Gerrit.
Implementation of the Gerrit ITS-Framework (hooks-its)
for the support of IBM Rational Team Concert (RTC).
Provides the implementation for the RTC API for
the main hooks-its functions:
> Configuration wizard during the Gerrit init steps
> Commit validation based on Issue-ID regex in Git comment
> Ability to trigger RTC workflow actions based on Gerrit review
> Bi-directional association of Git commits to RTC Issues
> Feedback on RTC of the Gerrit review actions with hyperlinks
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..beef00d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.classpath
+.project
+.settings
+target
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..11069ed
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+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.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..e863808
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,372 @@
+<?xml version="1.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.
+-->
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.googlesource.gerrit.plugins.its</groupId>
+ <artifactId>hooks-its-parent</artifactId>
+ <version>2.6-SNAPSHOT</version>
+ </parent>
+ <artifactId>hooks-rtc</artifactId>
+ <name>Gerrit Code Review - IBM Rational Team Concert support</name>
+ <url>http://maven.apache.org</url>
+ <properties>
+ <Gerrit-ApiType>plugin</Gerrit-ApiType>
+ <Gerrit-ApiVersion>${project.version}</Gerrit-ApiVersion>
+ <Gerrit-ReloadMode>reload</Gerrit-ReloadMode>
+ <Gerrit-InitStep>com.googlesource.gerrit.plugins.hooks.rtc.InitRTC</Gerrit-InitStep>
+ <Gerrit-Module>com.googlesource.gerrit.plugins.hooks.rtc.RTCModule</Gerrit-Module>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+ <licenses>
+ <license>
+ <name>Apache License, 2.0</name>
+ <comments>
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
+ </comments>
+ </license>
+ </licenses>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>axistools-maven-plugin</artifactId>
+ <version>1.3</version>
+ <dependencies>
+ <dependency>
+ <groupId>axis</groupId>
+ <artifactId>axis</artifactId>
+ <version>1.3</version>
+ </dependency>
+ </dependencies>
+ <configuration>
+ <wsdlFiles>
+ <wsdlFile>jirasoapservice-v2.wsdl</wsdlFile>
+ </wsdlFiles>
+ <packageSpace>com.atlassian.jira.rpc.soap.client</packageSpace>
+ </configuration>
+ <executions>
+ <execution>
+ <id>wsdl2java-generation</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>wsdl2java</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.4</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <configuration>
+ <promoteTransitiveDependencies>true</promoteTransitiveDependencies>
+ <artifactSet>
+ <excludes>
+ <exclude>com.google.gerrit:*</exclude>
+ <exclude>org.slf4j:*</exclude>
+ <exclude>com.google.guava:*</exclude>
+ <exclude>org.eclipse.jgit:*</exclude>
+ </excludes>
+ </artifactSet>
+ <transformers>
+ <transformer
+ implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <manifestEntries>
+ <Gerrit-Module>com.gerritforge.jira.client.ItsModule</Gerrit-Module>
+ <Implementation-Vendor>GerritForge LLP</Implementation-Vendor>
+ <Implementation-URL>http://www.gerritforge.com</Implementation-URL>
+ <Implementation-Title>Plugin ${project.artifactId}</Implementation-Title>
+ <Implementation-Version>${project.version}</Implementation-Version>
+ <Gerrit-ApiType>${Gerrit-ApiType}</Gerrit-ApiType>
+ <Gerrit-ApiVersion>${Gerrit-ApiVersion}</Gerrit-ApiVersion>
+ <Gerrit-ReloadMode>${Gerrit-ReloadMode}</Gerrit-ReloadMode>
+ <Gerrit-InitStep>${Gerrit-InitStep}</Gerrit-InitStep>
+ <Gerrit-Module>${Gerrit-Module}</Gerrit-Module>
+ </manifestEntries>
+ </transformer>
+ </transformers>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>2.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>4.2.4</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>1.0.4</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.17</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.1</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>hooks-its</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.11</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <version>1.9.5</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/InitRTC.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/InitRTC.java
new file mode 100644
index 0000000..23be84b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/InitRTC.java
@@ -0,0 +1,102 @@
+// 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.rtc;
+
+import java.io.IOException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gerrit.pgm.init.InitStep;
+import com.google.gerrit.pgm.init.Section;
+import com.google.gerrit.pgm.init.Section.Factory;
+import com.google.gerrit.pgm.util.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.rtc.network.RTCClient;
+import com.googlesource.gerrit.plugins.hooks.validation.ItsAssociationPolicy;
+
+/** Initialize the GitRepositoryManager configuration section. */
+@Singleton
+class InitRTC extends InitIts implements InitStep {
+ private static final Logger log = LoggerFactory.getLogger(InitRTC.class);
+ private static final String COMMENT_LINK_SECTION = "commentLink";
+ private final ConsoleUI ui;
+ private Section rtc;
+ private Section rtcComment;
+ private Factory sections;
+ private String rtcUrl;
+ private String rtcUsername;
+ private String rtcPassword;
+
+
+ @Inject
+ InitRTC(final ConsoleUI ui, final Section.Factory sections) {
+ this.ui = ui;
+ this.sections = sections;
+ }
+
+ public void run() {
+ this.rtc = sections.get(RTCItsFacade.ITS_NAME_RTC, null);
+ this.rtcComment =
+ sections.get(COMMENT_LINK_SECTION, RTCItsFacade.ITS_NAME_RTC);
+
+ ui.message("\n");
+ ui.header("IBM Rational Team Concert connectivity");
+
+ boolean sslVerify = true;
+ do {
+ rtcUrl = enterRTCConnectivity();
+ if(rtcUrl != null) {
+ sslVerify = enterSSLVerify(rtc);
+ }
+ } while (rtcUrl != null
+ && (isConnectivityRequested(ui, rtcUrl) && !isRTCConnectSuccessful(rtcUrl, sslVerify)));
+
+ if (rtcUrl == null) {
+ return;
+ }
+
+ ui.header("Rational Team Concert issue-tracking association");
+ rtcComment.string("RTC Issue-Id regex", "match", "RTC#([0-9]+)");
+ rtcComment.set("html",
+ String.format("<a href=\"%s/browse/$1\">$1</a>", rtcUrl));
+
+ rtcComment.select("RTC Issue-Id enforced in commit message", "association",
+ ItsAssociationPolicy.OPTIONAL);
+ }
+
+ public String enterRTCConnectivity() {
+ String url = rtc.string("RTC CCM URL (empty to skip)", "url", null);
+ if (url != null) {
+ rtcUsername = rtc.string("RTC username", "username", "");
+ rtcPassword = rtc.password("username", "password");
+ }
+ return url;
+ }
+
+ private boolean isRTCConnectSuccessful(String rtcUrl, boolean sslVerify) {
+ ui.message("Checking IBM Rational Team Concert connectivity ... ");
+ try {
+ RTCClient rtcClient = new RTCClient(rtcUrl, sslVerify, null);
+ rtcClient.sessionApi().login(rtcUsername, rtcPassword);
+ ui.message("[OK]\n");
+ return true;
+ } catch (IOException e) {
+ ui.message("*FAILED* (%s)\n", e.getLocalizedMessage());
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/RTCItsFacade.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/RTCItsFacade.java
new file mode 100644
index 0000000..54f1b96
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/RTCItsFacade.java
@@ -0,0 +1,148 @@
+// 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.rtc;
+
+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.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
+import com.googlesource.gerrit.plugins.hooks.rtc.network.RTCClient;
+import com.googlesource.gerrit.plugins.hooks.rtc.workitems.RtcComment;
+import com.googlesource.gerrit.plugins.hooks.rtc.workitems.RtcRelatedLink;
+import com.googlesource.gerrit.plugins.hooks.rtc.workitems.RtcWorkItem;
+
+public class RTCItsFacade implements ItsFacade {
+
+ public static final String ITS_NAME_RTC = "rtc";
+
+ public static final String GERRIT_CONFIG_RTC_USERNAME = "username";
+ public static final String GERRIT_CONFIG_RTC_PASSWORD = "password";
+ public static final String GERRIT_CONFIG_CCM_URL = "url";
+ public static final String GERRIT_CONFIG_SSL_VERIFY = "sslVerify";
+
+ private Logger log = LoggerFactory.getLogger(RTCItsFacade.class);
+
+ Config gerritConfig;
+
+ private RTCClient client;
+
+ private Injector injector;
+
+ @Inject
+ public RTCItsFacade(@GerritServerConfig Config gerritConfig, Injector injector) {
+ try {
+ this.injector = injector;
+ this.gerritConfig = gerritConfig;
+ client().ping();
+ log.info("Connected to RTC at " + getRtcUrl() + " as admin user "
+ + getRtcUser());
+ } catch (Exception ex) {
+ log.warn("RTC is currently not available", ex);
+ }
+ }
+
+ @Override
+ public String name() {
+ return "RTC";
+ }
+
+ @Override
+ public void addComment(String itemId, String comment) throws IOException {
+ long workItem = Long.parseLong(itemId);
+ log.debug("Adding comment " + comment + " to workItem " + workItem);
+ RtcComment rtcComment =
+ client().workItemsApi().addComment(workItem, comment);
+ log.debug("Comment created: " + rtcComment);
+ }
+
+ @Override
+ public void addRelatedLink(String itemId, URL relatedUrl, String description)
+ throws IOException {
+ long workItem = Long.parseLong(itemId);
+ log.debug("Adding related link " + relatedUrl + " to workItem " + workItem
+ + " with description " + description);
+ RtcRelatedLink relatedLink =
+ client().workItemsApi().addRelated(workItem, relatedUrl, description);
+ log.debug("Related link " + relatedLink + " to workItem#" + workItem
+ + " CREATED");
+ }
+
+ @Override
+ public void performAction(String itemId, String actionName)
+ throws IOException {
+ long workItem = Long.parseLong(itemId);
+ log.debug("Executing action " + actionName + " on workItem " + workItem);
+ RtcWorkItem wip = client().workItemsApi().getWorkItem(workItem);
+ log.debug(" - loaded workitem " + wip);
+ wip = client().workItemsApi().performAction(wip, actionName);
+ log.debug("New item state: : " + wip);
+ }
+
+ @Override
+ public String healthCheck(Check check) throws IOException {
+ client.ping();
+ return "{\"status\"=\"ok\",\"system\"=\"RTC\",}";
+ }
+
+ private RTCClient client() throws IOException {
+
+ if (client == null) {
+ client = injector.getInstance(RTCClient.class);
+ client.setLoginCredentials(getRtcUser(), getRtcPassword());
+
+ log.debug("RTC Client pointing to " + getRtcUrl() + " as " + getRtcUser());
+ }
+
+ return client;
+ }
+
+ private String getRtcPassword() {
+ return gerritConfig.getString(ITS_NAME_RTC, null,
+ GERRIT_CONFIG_RTC_PASSWORD);
+ }
+
+ private String getRtcUser() {
+ return gerritConfig.getString(ITS_NAME_RTC, null,
+ GERRIT_CONFIG_RTC_USERNAME);
+ }
+
+ private String getRtcUrl() {
+ return gerritConfig.getString(ITS_NAME_RTC, null, GERRIT_CONFIG_CCM_URL);
+ }
+
+ private boolean getSslVerify() {
+ return gerritConfig.getBoolean(ITS_NAME_RTC, null,
+ GERRIT_CONFIG_SSL_VERIFY, true);
+ }
+
+ @Override
+ public String createLinkForWebui(String url, String text) {
+ return "<a href=\"" + url + "\">" + text + "</a>";
+ }
+
+ @Override
+ public boolean exists(String itemId) throws IOException {
+ long workItem = Long.parseLong(itemId);
+ RtcWorkItem item = client().workItemsApi().getWorkItem(workItem);
+ return item != null;
+ }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/RTCModule.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/RTCModule.java
new file mode 100644
index 0000000..b5d85d7
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/RTCModule.java
@@ -0,0 +1,82 @@
+// 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.rtc;
+
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gerrit.common.ChangeListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.validators.CommitValidationListener;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
+import com.googlesource.gerrit.plugins.hooks.its.ItsName;
+import com.googlesource.gerrit.plugins.hooks.rtc.filters.RTCAddComment;
+import com.googlesource.gerrit.plugins.hooks.rtc.filters.RTCAddRelatedLinkToChangeId;
+import com.googlesource.gerrit.plugins.hooks.rtc.filters.RTCAddRelatedLinkToGitWeb;
+import com.googlesource.gerrit.plugins.hooks.rtc.filters.RTCChangeState;
+import com.googlesource.gerrit.plugins.hooks.validation.ItsValidateComment;
+
+public class RTCModule extends AbstractModule {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RTCModule.class);
+ private static final int THREAD_POOL_EXECUTORS = 10;
+
+ private final Config gerritConfig;
+
+ @Inject
+ public RTCModule(@GerritServerConfig final Config config) {
+ this.gerritConfig = config;
+ }
+
+ @Override
+ protected void configure() {
+ if (isConfigPresent(RTCItsFacade.ITS_NAME_RTC)) {
+ LOG.info("RTC is configured as ITS");
+ bind(ItsFacade.class).to(RTCItsFacade.class);
+
+ DynamicSet.bind(binder(), CommitValidationListener.class).to(
+ ItsValidateComment.class);
+
+ bind(ExecutorService.class).toInstance(
+ new ScheduledThreadPoolExecutor(THREAD_POOL_EXECUTORS));
+ bind(String.class).annotatedWith(ItsName.class).toInstance(
+ RTCItsFacade.ITS_NAME_RTC);
+
+ DynamicSet.bind(binder(), ChangeListener.class).to(
+ RTCAddRelatedLinkToChangeId.class);
+ DynamicSet.bind(binder(), ChangeListener.class).to(RTCAddComment.class);
+ DynamicSet.bind(binder(), ChangeListener.class).to(RTCChangeState.class);
+ DynamicSet.bind(binder(), ChangeListener.class).to(
+ RTCAddRelatedLinkToGitWeb.class);
+ }
+ }
+
+ private boolean isConfigPresent(String sectionName) {
+ Set<String> names = gerritConfig.getSections();
+ for (String name : names) {
+ if (name.equals(sectionName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/AbstractDeserializer.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/AbstractDeserializer.java
new file mode 100644
index 0000000..8b04a16
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/AbstractDeserializer.java
@@ -0,0 +1,56 @@
+// 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.rtc.api;
+
+import java.util.Calendar;
+
+import javax.xml.bind.DatatypeConverter;
+
+import com.google.gson.JsonObject;
+
+public class AbstractDeserializer {
+
+ protected final String extractRdfResourceUrl(JsonObject json, String memberName) {
+ JsonObject rdf = json.getAsJsonObject(memberName);
+ return rdf.get("rdf:resource").getAsString();
+ }
+
+ protected final Calendar extractDateTime(JsonObject root, final String memberName) {
+ String txt = extractString(root, memberName);
+ return DatatypeConverter.parseDateTime(txt);
+ }
+
+
+ protected final String extractString(JsonObject root, final String memberName) {
+ return root.get(memberName).getAsString();
+ }
+
+ protected final Long extractLong(JsonObject root, final String memberName) {
+ return root.get(memberName).getAsLong();
+ }
+
+ protected final String extractIdenFromRdfResource(JsonObject root, final String memberName) {
+ final String rdf = extractRdfResourceUrl(root, memberName);
+ if (rdf != null) {
+ return rdf.substring(rdf.lastIndexOf('/')+1);
+ }
+ else
+ return null;
+ }
+
+ protected String extractRdf(JsonObject root) {
+ return extractString(root, "rdf:resource");
+ }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/ResourceInvalidException.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/ResourceInvalidException.java
new file mode 100644
index 0000000..5816206
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/ResourceInvalidException.java
@@ -0,0 +1,25 @@
+// 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.rtc.api;
+
+import java.io.IOException;
+
+public class ResourceInvalidException extends IOException {
+
+ private static final long serialVersionUID = -2905132705439138798L;
+
+ public ResourceInvalidException(String name) {
+ super("Resource name "+name+" is not valid!");
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/ResourceModifiedException.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/ResourceModifiedException.java
new file mode 100644
index 0000000..65da3ce
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/ResourceModifiedException.java
@@ -0,0 +1,26 @@
+// 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.rtc.api;
+
+import java.io.IOException;
+import java.net.URI;
+
+public class ResourceModifiedException extends IOException {
+
+ private static final long serialVersionUID = -2905132705439138798L;
+
+ public ResourceModifiedException(URI uri) {
+ super("Resource modified at "+uri.toString()+ " - etag is not matching");
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/ResourceNotFoundException.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/ResourceNotFoundException.java
new file mode 100644
index 0000000..faa3d67
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/ResourceNotFoundException.java
@@ -0,0 +1,26 @@
+// 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.rtc.api;
+
+import java.io.IOException;
+import java.net.URI;
+
+public class ResourceNotFoundException extends IOException {
+
+ private static final long serialVersionUID = -2905132705439138798L;
+
+ public ResourceNotFoundException(URI uri) {
+ super("Resource not found at "+uri.toString());
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/RtcEntity.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/RtcEntity.java
new file mode 100644
index 0000000..684c7e0
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/RtcEntity.java
@@ -0,0 +1,43 @@
+// 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.rtc.api;
+
+
+public class RtcEntity extends RtcObject {
+
+ String id;
+ String title;
+
+ protected RtcEntity(String rdf) {
+ super(rdf);
+ }
+
+ public RtcEntity(String id, String title) {
+ this(null, id, title);
+ }
+
+ public RtcEntity(String rdf, String id, String title) {
+ super(rdf);
+ this.id = id;
+ this.title = title;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/RtcEntityDeserializer.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/RtcEntityDeserializer.java
new file mode 100644
index 0000000..d22f886
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/RtcEntityDeserializer.java
@@ -0,0 +1,39 @@
+// 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.rtc.api;
+
+import java.lang.reflect.Type;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.googlesource.gerrit.plugins.hooks.rtc.api.AbstractDeserializer;
+
+public class RtcEntityDeserializer extends AbstractDeserializer implements JsonDeserializer<RtcEntity> {
+
+ @Override
+ public RtcEntity deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) throws JsonParseException {
+
+ JsonObject root = json.getAsJsonObject();
+
+ RtcEntity result = new RtcEntity(extractRdf(root));
+ result.id = extractString(root, "dc:identifier");
+ result.title = extractString(root, "dc:title");
+
+ return result;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/RtcObject.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/RtcObject.java
new file mode 100644
index 0000000..1b37760
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/api/RtcObject.java
@@ -0,0 +1,48 @@
+// 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.rtc.api;
+
+import com.google.gson.Gson;
+import com.googlesource.gerrit.plugins.hooks.rtc.network.Transport;
+
+public class RtcObject {
+ private String rdf;
+ private String etag;
+
+ public RtcObject() {
+ this(null, null);
+ }
+
+ protected RtcObject(String rdf) {
+ this(rdf, Transport.etag.get());
+ }
+
+ protected RtcObject(String rdf, String etag) {
+ super();
+ this.rdf = rdf;
+ this.etag = etag;
+ }
+
+ public String getRdf() {
+ return rdf;
+ }
+
+ public String getEtag() {
+ return etag;
+ }
+
+ public String toString() {
+ return new Gson().toJson(this);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/filters/ChangeListenerAsyncDecorator.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/filters/ChangeListenerAsyncDecorator.java
new file mode 100644
index 0000000..43a8ef5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/filters/ChangeListenerAsyncDecorator.java
@@ -0,0 +1,72 @@
+// 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.rtc.filters;
+
+import java.util.ArrayList;
+import java.util.Queue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gerrit.common.ChangeListener;
+import com.google.gerrit.server.events.ChangeEvent;
+
+public class ChangeListenerAsyncDecorator<T extends ChangeListener> implements
+ ChangeListener {
+ private static final int MAX_PENDING_EVENTS = 1024;
+ private static final int MAX_BATCH_SIZE = 64;
+ private static final Logger log = LoggerFactory
+ .getLogger(ChangeListenerAsyncDecorator.class);
+ private T innerListener;
+ private final LinkedBlockingQueue<ChangeEvent> queue =
+ new LinkedBlockingQueue<ChangeEvent>(MAX_PENDING_EVENTS);
+ private ExecutorService executor;
+
+ public class ChangeRunner implements Runnable {
+ @Override
+ public void run() {
+ ArrayList<ChangeEvent> failedEvents = new ArrayList<ChangeEvent>();
+ for (int i = 0; !queue.isEmpty() && i < MAX_BATCH_SIZE; i++) {
+ ChangeEvent event = queue.remove();
+ try {
+ innerListener.onChangeEvent(event);
+ } catch (Throwable e) {
+ log.error("Execution of event " + event.getClass().getName() + "/"
+ + event.toString()
+ + " FAILED\nEvent requeued for later execution", event);
+ failedEvents.add(event);
+ }
+ }
+
+ queue.addAll(failedEvents);
+ }
+ }
+
+ public ChangeListenerAsyncDecorator(T innerListener, ExecutorService executor) {
+ this.innerListener = innerListener;
+ this.executor = executor;
+ }
+
+ @Override
+ public void onChangeEvent(ChangeEvent event) {
+ queue.add(event);
+ executor.submit(new ChangeRunner());
+ }
+
+ public Queue<ChangeEvent> getQueue() {
+ return queue;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/filters/RTCAddComment.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/filters/RTCAddComment.java
new file mode 100644
index 0000000..8e2d92d
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/filters/RTCAddComment.java
@@ -0,0 +1,32 @@
+// 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.rtc.filters;
+
+import java.util.concurrent.ExecutorService;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.hooks.workflow.GerritHookFilterAddComment;
+
+@Singleton
+public class RTCAddComment extends
+ ChangeListenerAsyncDecorator<GerritHookFilterAddComment> {
+
+ @Inject
+ public RTCAddComment(GerritHookFilterAddComment innerListener,
+ ExecutorService executor) {
+ super(innerListener, executor);
+ }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/filters/RTCAddRelatedLinkToChangeId.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/filters/RTCAddRelatedLinkToChangeId.java
new file mode 100644
index 0000000..cfaad15
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/filters/RTCAddRelatedLinkToChangeId.java
@@ -0,0 +1,32 @@
+// 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.rtc.filters;
+
+import java.util.concurrent.ExecutorService;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.hooks.workflow.GerritHookFilterAddRelatedLinkToChangeId;
+
+@Singleton
+public class RTCAddRelatedLinkToChangeId extends
+ ChangeListenerAsyncDecorator<GerritHookFilterAddRelatedLinkToChangeId> {
+
+ @Inject
+ public RTCAddRelatedLinkToChangeId(
+ GerritHookFilterAddRelatedLinkToChangeId innerListener,
+ ExecutorService executor) {
+ super(innerListener, executor);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/filters/RTCAddRelatedLinkToGitWeb.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/filters/RTCAddRelatedLinkToGitWeb.java
new file mode 100644
index 0000000..6f94e43
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/filters/RTCAddRelatedLinkToGitWeb.java
@@ -0,0 +1,32 @@
+// 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.rtc.filters;
+
+import java.util.concurrent.ExecutorService;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.hooks.workflow.GerritHookFilterAddRelatedLinkToGitWeb;
+
+@Singleton
+public class RTCAddRelatedLinkToGitWeb extends
+ ChangeListenerAsyncDecorator<GerritHookFilterAddRelatedLinkToGitWeb> {
+
+ @Inject
+ public RTCAddRelatedLinkToGitWeb(
+ GerritHookFilterAddRelatedLinkToGitWeb innerListener,
+ ExecutorService executor) {
+ super(innerListener, executor);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/filters/RTCChangeState.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/filters/RTCChangeState.java
new file mode 100644
index 0000000..63a7f15
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/filters/RTCChangeState.java
@@ -0,0 +1,31 @@
+// 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.rtc.filters;
+
+import java.util.concurrent.ExecutorService;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.hooks.workflow.GerritHookFilterChangeState;
+
+@Singleton
+public class RTCChangeState extends
+ ChangeListenerAsyncDecorator<GerritHookFilterChangeState> {
+
+ @Inject
+ public RTCChangeState(GerritHookFilterChangeState innerListener,
+ ExecutorService executor) {
+ super(innerListener, executor);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/AuthenticationException.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/AuthenticationException.java
new file mode 100644
index 0000000..99361c9
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/AuthenticationException.java
@@ -0,0 +1,24 @@
+// 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.rtc.network;
+
+import java.io.IOException;
+
+public class AuthenticationException extends IOException {
+ private static final long serialVersionUID = -4713503914827418174L;
+
+ public AuthenticationException(String reason) {
+ super(reason);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/CachableResourcesFactory.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/CachableResourcesFactory.java
new file mode 100644
index 0000000..4aba41d
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/CachableResourcesFactory.java
@@ -0,0 +1,72 @@
+// 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.rtc.network;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class CachableResourcesFactory {
+
+ public final Map<Class<?>, InternalFactory<?>> factories;
+ private Transport transport;
+
+ public CachableResourcesFactory(Transport transport) {
+ this.transport = transport;
+ this.factories = new ConcurrentHashMap<Class<?>, InternalFactory<?>>();
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T get(String url, Class<T> clazz) throws IOException {
+ return (T) getFactory(clazz).get(url);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> InternalFactory<?> getFactory(Class<T> clazz) {
+ InternalFactory<?> factory = factories.get(clazz);
+ if (factory == null) {
+ factory = new InternalFactory(transport, clazz);
+ factories.put(clazz, factory);
+ }
+ return factory;
+ }
+
+ private static class InternalFactory<T> {
+
+ private final Transport transport;
+ private final Map<String, T> cache;
+ private Class<T> clazz;
+
+ private InternalFactory(Transport transport, Class<T> clazz) {
+ this.transport = transport;
+ this.clazz = clazz;
+ this.cache = new ConcurrentHashMap<String, T>();
+ }
+
+ private T get(final String url) throws IOException {
+ T result = cache.get(url);
+ if (result == null) {
+ result = streamResourceFrom(url, clazz);
+ cache.put(url, result);
+ }
+
+ return result;
+ }
+
+ private T streamResourceFrom(final String url, final Class<T> clazz) throws IOException {
+ return transport.get(url+".json", clazz);
+ }
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/HttpPatch.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/HttpPatch.java
new file mode 100644
index 0000000..03b6ec5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/HttpPatch.java
@@ -0,0 +1,38 @@
+// 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.rtc.network;
+
+import java.net.URI;
+
+import org.apache.http.client.methods.HttpPost;
+
+public class HttpPatch extends HttpPost {
+
+ public HttpPatch() {
+ super();
+ }
+
+ public HttpPatch(String uri) {
+ super(uri);
+ }
+
+ public HttpPatch(URI uri) {
+ super(uri);
+ }
+
+ @Override
+ public String getMethod() {
+ return "PATCH";
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/InvalidContentTypeException.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/InvalidContentTypeException.java
new file mode 100644
index 0000000..67bc7cd
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/InvalidContentTypeException.java
@@ -0,0 +1,24 @@
+// 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.rtc.network;
+
+import java.io.IOException;
+
+public class InvalidContentTypeException extends IOException {
+ private static final long serialVersionUID = -5010337914983644879L;
+
+ public InvalidContentTypeException(String reason) {
+ super(reason);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/RTCClient.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/RTCClient.java
new file mode 100644
index 0000000..0c57f60
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/RTCClient.java
@@ -0,0 +1,230 @@
+// 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.rtc.network;
+
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.HttpResponse;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.impl.client.BasicCookieStore;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.client.DefaultRedirectHandler;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+import org.eclipse.jgit.lib.Config;
+
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.hooks.rtc.RTCItsFacade;
+import com.googlesource.gerrit.plugins.hooks.rtc.api.RtcEntity;
+import com.googlesource.gerrit.plugins.hooks.rtc.api.RtcEntityDeserializer;
+import com.googlesource.gerrit.plugins.hooks.rtc.session.SessionApi;
+import com.googlesource.gerrit.plugins.hooks.rtc.session.SessionApiImpl;
+import com.googlesource.gerrit.plugins.hooks.rtc.workitems.RtcComment;
+import com.googlesource.gerrit.plugins.hooks.rtc.workitems.RtcCommentDeserializer;
+import com.googlesource.gerrit.plugins.hooks.rtc.workitems.RtcRelatedLink;
+import com.googlesource.gerrit.plugins.hooks.rtc.workitems.RtcRelatedLinkDeserializer;
+import com.googlesource.gerrit.plugins.hooks.rtc.workitems.RtcWorkItem;
+import com.googlesource.gerrit.plugins.hooks.rtc.workitems.RtcWorkItemDeserializer;
+import com.googlesource.gerrit.plugins.hooks.rtc.workitems.RtcWorkflowAction;
+import com.googlesource.gerrit.plugins.hooks.rtc.workitems.RtcWorkflowActionDeserializer;
+import com.googlesource.gerrit.plugins.hooks.rtc.workitems.WorkItemsApi;
+import com.googlesource.gerrit.plugins.hooks.rtc.workitems.WorkItemsApiImpl;
+
+public class RTCClient {
+
+ private static Log LOG = LogFactory.getLog(RTCClient.class);
+
+ private String baseUrl;
+ private DefaultHttpClient httpclient;
+ private BasicCookieStore cookieStore;
+ private Gson gson;
+
+ private Transport transport;
+ private CachableResourcesFactory factory;
+
+ private SessionApi sessionApi;
+ private WorkItemsApi workItemsApi;
+
+ private boolean loggedIn;
+
+ private String rtcUser;
+
+ private String rtcPassword;
+
+ @Inject
+ public RTCClient(@GerritServerConfig Config config, RTCHttpParams httpParams)
+ throws IOException {
+ this(config.getString(RTCItsFacade.ITS_NAME_RTC, null, "url"), config
+ .getBoolean(RTCItsFacade.ITS_NAME_RTC, null, "sslVerify", true),
+ httpParams);
+ }
+
+ public RTCClient(String url, boolean sslVerify, HttpParams httpParams)
+ throws IOException {
+ super();
+
+ this.baseUrl = (url.endsWith("/") ? url.substring(url.length() - 1) : url);
+ if (httpParams == null) {
+ httpParams = new BasicHttpParams();
+ }
+ SchemeRegistry schemeRegistry = new SchemeRegistry();
+ schemeRegistry.register(new Scheme("http", PlainSocketFactory
+ .getSocketFactory(), 80));
+ this.httpclient =
+ new DefaultHttpClient(new ThreadSafeClientConnManager(httpParams,
+ schemeRegistry), httpParams);
+
+ this.transport = new Transport(this, baseUrl, httpclient, httpParams);
+ this.factory = new CachableResourcesFactory(transport);
+
+ GsonBuilder builder = new GsonBuilder();
+ builder.registerTypeAdapter(RtcWorkItem.class, new RtcWorkItemDeserializer(
+ factory));
+ builder.registerTypeAdapter(RtcComment.class, new RtcCommentDeserializer());
+ builder.registerTypeAdapter(RtcEntity.class, new RtcEntityDeserializer());
+ builder.registerTypeAdapter(RtcRelatedLink.class,
+ new RtcRelatedLinkDeserializer());
+ builder.registerTypeAdapter(RtcWorkflowAction.class,
+ new RtcWorkflowActionDeserializer());
+ gson = builder.create();
+ transport.setGson(gson);
+
+ setCookieStore();
+ setRedirectStategy();
+ setSSLTrustStrategy(sslVerify);
+
+ sessionApi = new SessionApiImpl(this, transport);
+ workItemsApi = new WorkItemsApiImpl(this, transport);
+ }
+
+ public boolean isLoggedIn() {
+ return loggedIn;
+ }
+
+ public SessionApi sessionApi() throws IOException {
+ collectSessionCookie();
+ return sessionApi;
+ }
+
+ public WorkItemsApi workItemsApi() throws IOException {
+ collectSessionCookie();
+ return workItemsApi;
+ }
+
+ private void collectSessionCookie() throws IOException {
+ if (cookieStore.getCookies().size() <= 0) {
+ LOG.debug("Initial collecting of session cookie...");
+ transport.get(baseUrl);
+ LOG.debug("Succesfully collected cookies: " + cookieStore.getCookies());
+ }
+ }
+
+ private void setCookieStore() {
+ cookieStore = new BasicCookieStore();
+ httpclient.setCookieStore(cookieStore);
+ }
+
+ private void setSSLTrustStrategy(boolean sslVerify) throws IOException {
+ try {
+ TrustManager[] trustAllCerts =
+ new TrustManager[] {new X509TrustManager() {
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+
+ public void checkClientTrusted(X509Certificate[] certs,
+ String authType) {
+ }
+
+ public void checkServerTrusted(X509Certificate[] certs,
+ String authType) {
+ }
+ }};
+ SSLContext sc;
+
+ if (sslVerify) {
+ sc = SSLContext.getDefault();
+ } else {
+ sc = SSLContext.getInstance("SSL");
+ sc.init(null, trustAllCerts, new SecureRandom());
+ }
+
+ SSLSocketFactory sf = new SSLSocketFactory(sc);
+ sf.setHostnameVerifier(new AllowAllHostnameVerifier());
+ SchemeRegistry schemeRegistry =
+ httpclient.getConnectionManager().getSchemeRegistry();
+ schemeRegistry.register(new Scheme("https", sf, 443));
+ } catch (Exception any) {
+ throw new IOException(any);
+ }
+ }
+
+ private void setRedirectStategy() {
+ httpclient.setRedirectHandler(new DefaultRedirectHandler() {
+ @Override
+ public boolean isRedirectRequested(HttpResponse response,
+ HttpContext context) {
+ boolean isRedirect = super.isRedirectRequested(response, context);
+ if (!isRedirect) {
+ int responseCode = response.getStatusLine().getStatusCode();
+ if (responseCode == 301 || responseCode == 302) {
+ return true;
+ }
+ }
+ return isRedirect;
+ }
+ });
+ }
+
+ public Transport getTransportForTest() {
+ return transport;
+ }
+
+ public void setLoginCredentials(String rtcUser, String rtcPassword) throws IOException {
+ this.rtcUser = rtcUser;
+ this.rtcPassword = rtcPassword;
+ }
+
+ public void login() throws IOException {
+ sessionApi().login(rtcUser, rtcPassword);
+ loggedIn = true;
+ }
+
+ public void ping() throws IOException {
+ if (loggedIn) {
+ sessionApi().ping();
+ }
+ }
+
+ public void setLoggedIn(boolean b) {
+ loggedIn = false;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/RTCHttpParams.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/RTCHttpParams.java
new file mode 100644
index 0000000..ed0d839
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/RTCHttpParams.java
@@ -0,0 +1,209 @@
+// 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.rtc.network;
+
+import java.util.HashMap;
+
+import org.apache.http.client.params.ClientPNames;
+import org.apache.http.conn.params.ConnManagerPNames;
+import org.apache.http.params.CoreConnectionPNames;
+import org.apache.http.params.CoreProtocolPNames;
+import org.apache.http.params.HttpParams;
+import org.eclipse.jgit.lib.Config;
+
+import com.google.common.base.Objects;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.hooks.rtc.RTCItsFacade;
+
+@SuppressWarnings("deprecation")
+public class RTCHttpParams implements HttpParams {
+ private static interface ParameterParser {
+ public Object parse(String value);
+ }
+
+ private static final ParameterParser TYPE_STRING = new ParameterParser() {
+ @Override
+ public Object parse(String value) {
+ return value;
+ }
+ };
+
+ private static final ParameterParser TYPE_LONG = new ParameterParser() {
+ @Override
+ public Object parse(String value) {
+ if (value == null) {
+ return null;
+ } else {
+ return Long.parseLong(value);
+ }
+ }
+ };
+
+ private static final ParameterParser TYPE_INT = new ParameterParser() {
+ @Override
+ public Object parse(String value) {
+ if (value == null) {
+ return null;
+ } else {
+ return Integer.parseInt(value);
+ }
+ }
+ };
+
+ private static final ParameterParser TYPE_BOOL = new ParameterParser() {
+ @Override
+ public Object parse(String value) {
+ if (value == null) {
+ return null;
+ } else {
+ return Boolean.parseBoolean(value);
+ }
+ }
+ };
+
+ private static final HashMap<String, ParameterParser> TYPES =
+ new HashMap<String, RTCHttpParams.ParameterParser>();
+ static {
+
+ TYPES.put(CoreConnectionPNames.SO_TIMEOUT, TYPE_INT);
+ TYPES.put(CoreConnectionPNames.TCP_NODELAY, TYPE_BOOL);
+ TYPES.put(CoreConnectionPNames.SOCKET_BUFFER_SIZE, TYPE_INT);
+ TYPES.put(CoreConnectionPNames.SO_LINGER, TYPE_INT);
+ TYPES.put(CoreConnectionPNames.SO_REUSEADDR, TYPE_BOOL);
+ TYPES.put(CoreConnectionPNames.CONNECTION_TIMEOUT, TYPE_INT);
+ TYPES.put(CoreConnectionPNames.STALE_CONNECTION_CHECK, TYPE_BOOL);
+ TYPES.put(CoreConnectionPNames.MAX_LINE_LENGTH, TYPE_INT);
+ TYPES.put(CoreConnectionPNames.MAX_HEADER_COUNT, TYPE_INT);
+ TYPES.put(CoreConnectionPNames.MIN_CHUNK_LIMIT, TYPE_INT);
+ TYPES.put(CoreConnectionPNames.SO_KEEPALIVE, TYPE_BOOL);
+
+ TYPES.put(ClientPNames.HANDLE_REDIRECTS, TYPE_BOOL);
+ TYPES.put(ClientPNames.REJECT_RELATIVE_REDIRECT, TYPE_BOOL);
+ TYPES.put(ClientPNames.MAX_REDIRECTS, TYPE_INT);
+ TYPES.put(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, TYPE_BOOL);
+ TYPES.put(ClientPNames.HANDLE_AUTHENTICATION, TYPE_BOOL);
+ TYPES.put(ClientPNames.CONN_MANAGER_TIMEOUT, TYPE_LONG);
+
+ TYPES.put(CoreProtocolPNames.STRICT_TRANSFER_ENCODING, TYPE_BOOL);
+ TYPES.put(CoreProtocolPNames.USE_EXPECT_CONTINUE, TYPE_BOOL);
+ TYPES.put(CoreProtocolPNames.WAIT_FOR_CONTINUE, TYPE_INT);
+
+ TYPES.put(ConnManagerPNames.TIMEOUT, TYPE_LONG);
+ TYPES.put(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, TYPE_INT);
+ }
+ private Config config;
+
+ @Inject
+ public RTCHttpParams(@GerritServerConfig Config config) {
+ this.config = config;
+ }
+
+ private String getParamName(String name) {
+ StringBuilder camelisedName = new StringBuilder();
+ for (String namePart : name.split("[\\.-]")) {
+ if (camelisedName.length() > 0) {
+ camelisedName.append(Character.toUpperCase(namePart.charAt(0)));
+ camelisedName.append(namePart.substring(1, namePart.length()));
+ } else {
+ camelisedName.append(namePart);
+ }
+ }
+ return camelisedName.toString();
+ }
+
+ @Override
+ public Object getParameter(String name) {
+ String value =
+ config.getString(RTCItsFacade.ITS_NAME_RTC, null, getParamName(name));
+ return getParameterParser(name).parse(value);
+ }
+
+ private ParameterParser getParameterParser(String name) {
+ return Objects.firstNonNull(TYPES.get(name), TYPE_STRING);
+ }
+
+ @Override
+ public HttpParams setParameter(String name, Object value) {
+ return throwsNotSupported(HttpParams.class);
+ }
+
+ private <T> T throwsNotSupported(Class<T> clazz) {
+ throw new IllegalArgumentException("Method not supported");
+ }
+
+ @Override
+ @Deprecated
+ public HttpParams copy() {
+ return throwsNotSupported(HttpParams.class);
+ }
+
+ @Override
+ public boolean removeParameter(String name) {
+ return throwsNotSupported(Boolean.class);
+ }
+
+ @Override
+ public long getLongParameter(String name, long defaultValue) {
+ return config.getLong(RTCItsFacade.ITS_NAME_RTC, null, getParamName(name),
+ defaultValue);
+ }
+
+ @Override
+ public HttpParams setLongParameter(String name, long value) {
+ return throwsNotSupported(HttpParams.class);
+ }
+
+ @Override
+ public int getIntParameter(String name, int defaultValue) {
+ return config.getInt(RTCItsFacade.ITS_NAME_RTC, null, getParamName(name),
+ defaultValue);
+ }
+
+ @Override
+ public HttpParams setIntParameter(String name, int value) {
+ return throwsNotSupported(HttpParams.class);
+ }
+
+ @Override
+ public double getDoubleParameter(String name, double defaultValue) {
+ return throwsNotSupported(Double.class);
+ }
+
+ @Override
+ public HttpParams setDoubleParameter(String name, double value) {
+ return throwsNotSupported(HttpParams.class);
+ }
+
+ @Override
+ public boolean getBooleanParameter(String name, boolean defaultValue) {
+ return config.getBoolean(RTCItsFacade.ITS_NAME_RTC, null,
+ getParamName(name), defaultValue);
+ }
+
+ @Override
+ public HttpParams setBooleanParameter(String name, boolean value) {
+ return throwsNotSupported(HttpParams.class);
+ }
+
+ @Override
+ public boolean isParameterTrue(String name) {
+ return getBooleanParameter(name, false);
+ }
+
+ @Override
+ public boolean isParameterFalse(String name) {
+ return !isParameterTrue(name);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/Transport.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/Transport.java
new file mode 100644
index 0000000..fb50058
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/network/Transport.java
@@ -0,0 +1,282 @@
+// 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.rtc.network;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.net.MalformedURLException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HTTP;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.googlesource.gerrit.plugins.hooks.rtc.api.ResourceNotFoundException;
+
+public class Transport {
+
+ public static final String APP_JSON = "application/json";
+ public static final String ANY = "*/*";
+ public static final String APP_OSLC =
+ "application/x-oslc-cm-change-request+json";
+
+ private static final Log log = LogFactory.getLog(Transport.class);
+
+ public static final ThreadLocal<String> etag = new ThreadLocal<String>();
+
+ protected HttpClient httpclient;
+ protected Gson gson;
+ protected String baseUrl;
+ private HttpParams httpParams;
+ private RTCClient rtcClient;
+
+ public Transport(RTCClient rtcClient, String baseUrl, DefaultHttpClient httpclient, HttpParams httpParams) {
+ this.rtcClient = rtcClient;
+ this.baseUrl = baseUrl;
+ this.httpclient = httpclient;
+ this.httpParams = httpParams;
+ }
+
+ public void setGson(Gson gson) {
+ this.gson = gson;
+ }
+
+ public <T> T put(final String path, final Type typeOrClass, JsonObject data,
+ String etag) throws IOException {
+ HttpPut request = new HttpPut(toUri(path));
+ if (log.isDebugEnabled())
+ log.debug("Preparing PUT against " + request.getURI() + " using etag "
+ + etag + " and data " + data);
+ request.setEntity(new StringEntity(data.toString(), HTTP.UTF_8));
+ if (etag != null) request.addHeader("If-Match", etag);
+ return invoke(request, typeOrClass, APP_OSLC, APP_OSLC);
+ }
+
+ public <T> T patch(final String path, final Type typeOrClass,
+ JsonObject data, String etag) throws IOException {
+ HttpPatch request = newHttpPatch(path);
+ if (log.isDebugEnabled())
+ log.debug("Preparing PATCH against " + request.getURI() + " using etag "
+ + etag + " and data " + data);
+ request.setEntity(new StringEntity(data.toString(), HTTP.UTF_8));
+ if (etag != null) request.addHeader("If-Match", etag);
+ return invoke(request, typeOrClass, APP_OSLC, APP_OSLC);
+ }
+
+ public <T> T post(final String path, final Type typeOrClass,
+ String contentType,
+ final NameValuePair... params) throws IOException {
+ HttpPost request = newHttpPost(path);
+ if (log.isDebugEnabled())
+ log.debug("Preparing POST against " + request.getURI() + " using params "
+ + Arrays.asList(params));
+ List<NameValuePair> nameValuePairs = Arrays.asList(params);
+ request.setEntity(new UrlEncodedFormEntity(nameValuePairs, HTTP.UTF_8));
+ return invoke(request, typeOrClass, contentType, null);
+ }
+
+ public <T> T get(final String path, final Type typeOrClass)
+ throws IOException, MalformedURLException {
+ final HttpGet request = newHttpGet(path);
+ if (log.isDebugEnabled())
+ log.debug("Preparing GET against " + request.getURI());
+ return invoke(request, typeOrClass, APP_JSON, null);
+ }
+
+ public String get(final String path) throws IOException,
+ MalformedURLException {
+ final HttpGet request = newHttpGet(path);
+ if (log.isDebugEnabled())
+ log.debug("Preparing GET against " + request.getURI());
+ return invoke(request, null, ANY, null);
+ }
+
+ @SuppressWarnings("unchecked")
+ private synchronized <T> T invoke(HttpRequestBase request,
+ Object typeOrClass, String acceptType, String contentType)
+ throws IOException, ClientProtocolException, ResourceNotFoundException {
+
+ if (contentType != null) {
+ request.addHeader("Content-Type", contentType);
+ }
+
+ if (acceptType != null) {
+ request.addHeader("Accept", acceptType);
+ }
+
+ HttpResponse response = httpclient.execute(request);
+ try {
+ final int code = response.getStatusLine().getStatusCode();
+ if (code / 100 != 2) {
+ if (code == 404) {
+ log.debug("API call failed: " + response.getStatusLine());
+ throw new ResourceNotFoundException(request.getURI());
+ } else {
+ log.debug("API call failed: " + response.getStatusLine());
+ throw new IOException("API call failed! " + response.getStatusLine());
+ }
+ }
+
+ String responseContentTypeString = getResponseContentType(response);
+ String entityString = readEntityAsString(response);
+
+ if (!assertValidContentType(acceptType, responseContentTypeString)) {
+ log.error("Request to " + request.getURI()
+ + " failed because of an invalid content returned:\n"
+ + entityString);
+ rtcClient.setLoggedIn(false);
+ throw new InvalidContentTypeException("Wrong content type '"
+ + responseContentTypeString + "' in HTTP response (Expected: "
+ + acceptType + ")");
+ }
+
+ if (typeOrClass != null && acceptType.endsWith("json")
+ && responseContentTypeString.endsWith("json")) {
+ Transport.etag.set(extractEtag(response));
+ if (typeOrClass instanceof ParameterizedType) {
+ return gson.fromJson(entityString, (Type) typeOrClass);
+ } else {
+ return gson.fromJson(entityString, (Class<T>) typeOrClass);
+ }
+ } else if (typeOrClass != null && typeOrClass.equals(String.class)) {
+ return (T) entityString;
+ } else {
+ if (log.isDebugEnabled()) log.debug(entityString);
+ return null;
+ }
+ } finally {
+ consumeHttpEntity(response.getEntity());
+ Transport.etag.set(null);
+ }
+ }
+
+ private boolean assertValidContentType(String acceptType,
+ String responseContentTypeString) throws IOException {
+ if (acceptType == null) {
+ return true;
+ }
+ if (acceptType.endsWith("/*")) {
+ return true;
+ }
+ if (acceptType.split("/")[1].equalsIgnoreCase(responseContentTypeString
+ .split("/")[1])) {
+ return true;
+ }
+ return false;
+ }
+
+ public String getResponseContentType(HttpResponse response) {
+ Header contentType = response.getEntity().getContentType();
+ if (contentType == null) {
+ return null;
+ }
+
+ String contentTypeValue = contentType.getValue();
+ if (contentTypeValue == null) {
+ return null;
+ }
+
+ for (String contentTypeItem : contentTypeValue.split(";")) {
+ if (contentTypeItem.indexOf('/') >= 0) {
+ return contentTypeItem;
+ }
+ }
+ return null;
+ }
+
+ private String readEntityAsString(HttpResponse response)
+ throws IllegalStateException, IOException {
+ String charset = "utf-8";
+ Header[] contentTypes = response.getHeaders("Content-Type");
+ for (Header header : contentTypes) {
+ if (header.getName().equalsIgnoreCase("charset")) {
+ charset = header.getValue();
+ }
+ }
+
+ ByteArrayOutputStream responseOut = new ByteArrayOutputStream();
+ try {
+ IOUtils.copy(response.getEntity().getContent(), responseOut);
+ } finally {
+ responseOut.close();
+ }
+
+ return new String(responseOut.toByteArray(), Charset.forName(charset));
+ }
+
+ // We can't use the HTTP Client 4.2 EntityUtils.consume()
+ // because of compatibility issues with gerrit-pgm 2.5
+ // that includes httpclient 4.0
+ private void consumeHttpEntity(final HttpEntity entity) throws IOException {
+ if (entity == null) {
+ return;
+ }
+ if (entity.isStreaming()) {
+ InputStream instream = entity.getContent();
+ if (instream != null) {
+ instream.close();
+ }
+ }
+ }
+
+ private String extractEtag(HttpResponse response) {
+ final Header etagHeader = response.getFirstHeader("ETag");
+ return etagHeader == null ? null : etagHeader.getValue().substring(1,
+ etagHeader.getValue().length() - 1);
+ }
+
+ private HttpGet newHttpGet(final String path) throws MalformedURLException {
+ HttpGet get = new HttpGet(toUri(path));
+ get.setParams(httpParams);
+ return get;
+ }
+
+ private HttpPost newHttpPost(final String path) throws MalformedURLException {
+ return new HttpPost(toUri(path));
+ }
+
+ private HttpPatch newHttpPatch(final String path)
+ throws MalformedURLException {
+ return new HttpPatch(toUri(path));
+ }
+
+ private String toUri(final String path) throws MalformedURLException {
+ if (path.startsWith(baseUrl))
+ return path;
+ else
+ return baseUrl + path;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/session/RtcSession.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/session/RtcSession.java
new file mode 100644
index 0000000..3648d5e
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/session/RtcSession.java
@@ -0,0 +1,44 @@
+// 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.rtc.session;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.googlesource.gerrit.plugins.hooks.rtc.api.RtcObject;
+
+
+public class RtcSession extends RtcObject {
+
+ private String userId;
+ private String[] roles;
+
+ private RtcSession() {
+ super();
+ }
+
+ public RtcSession(String userId, String[] roles) {
+ this();
+ this.userId = userId;
+ this.roles = roles;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public List<String> getRoles() {
+ return Arrays.asList(roles);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/session/SessionApi.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/session/SessionApi.java
new file mode 100644
index 0000000..3d464ab
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/session/SessionApi.java
@@ -0,0 +1,24 @@
+// 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.rtc.session;
+
+import java.io.IOException;
+
+public interface SessionApi {
+
+ void login(String username, String password) throws IOException;
+
+ void ping() throws IOException;
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/session/SessionApiImpl.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/session/SessionApiImpl.java
new file mode 100644
index 0000000..1eb5d68
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/session/SessionApiImpl.java
@@ -0,0 +1,53 @@
+// 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.rtc.session;
+
+import java.io.IOException;
+
+import org.apache.http.message.BasicNameValuePair;
+
+import com.googlesource.gerrit.plugins.hooks.rtc.network.AuthenticationException;
+import com.googlesource.gerrit.plugins.hooks.rtc.network.InvalidContentTypeException;
+import com.googlesource.gerrit.plugins.hooks.rtc.network.RTCClient;
+import com.googlesource.gerrit.plugins.hooks.rtc.network.Transport;
+import com.googlesource.gerrit.plugins.hooks.rtc.workitems.AbstractApiImpl;
+
+public class SessionApiImpl extends AbstractApiImpl implements SessionApi {
+
+ private final Transport transport;
+
+ public SessionApiImpl(RTCClient rtcClient, Transport transport) {
+ super(rtcClient);
+ this.transport=transport;
+ }
+
+ @Override
+ public synchronized void login(String username, String password)
+ throws IOException {
+ String result = transport.post("/authenticated/j_security_check", String.class,
+ Transport.ANY,
+ new BasicNameValuePair("j_username", username),
+ new BasicNameValuePair("j_password", password));
+
+ if(result.indexOf("net.jazz.web.app.authfailed") > 0) {
+ throw new AuthenticationException("User authentication failed");
+ }
+ }
+
+ @Override
+ public void ping() throws IOException {
+ loginIfNeeded();
+ transport.get("/authenticated/identity");
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/AbstractApiImpl.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/AbstractApiImpl.java
new file mode 100644
index 0000000..8fa6d93
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/AbstractApiImpl.java
@@ -0,0 +1,32 @@
+// 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.rtc.workitems;
+
+import java.io.IOException;
+
+import com.googlesource.gerrit.plugins.hooks.rtc.network.RTCClient;
+
+public class AbstractApiImpl {
+ protected RTCClient client;
+
+ public AbstractApiImpl(RTCClient client) {
+ this.client = client;
+ }
+
+ protected void loginIfNeeded() throws IOException {
+ if(!client.isLoggedIn()) {
+ client.login();
+ }
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcComment.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcComment.java
new file mode 100644
index 0000000..cc8b779
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcComment.java
@@ -0,0 +1,47 @@
+// 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.rtc.workitems;
+
+import java.util.Calendar;
+
+import com.googlesource.gerrit.plugins.hooks.rtc.api.RtcObject;
+
+public class RtcComment extends RtcObject {
+
+ String creator;
+ String contents;
+ Calendar created;
+
+ RtcComment(String rdf) {
+ super(rdf);
+ }
+
+ public RtcComment(String creator, String contents) {
+ super();
+ this.creator = creator;
+ this.contents = contents;
+ }
+
+ public String getCreator() {
+ return creator;
+ }
+
+ public String getContents() {
+ return contents;
+ }
+
+ public Calendar getCreated() {
+ return created;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcCommentDeserializer.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcCommentDeserializer.java
new file mode 100644
index 0000000..b088c77
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcCommentDeserializer.java
@@ -0,0 +1,39 @@
+// 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.rtc.workitems;
+
+import java.lang.reflect.Type;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.googlesource.gerrit.plugins.hooks.rtc.api.AbstractDeserializer;
+
+public class RtcCommentDeserializer extends AbstractDeserializer implements JsonDeserializer<RtcComment> {
+
+ @Override
+ public RtcComment deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) throws JsonParseException {
+
+ JsonObject root = json.getAsJsonObject();
+
+ RtcComment result = new RtcComment(extractRdf(root));
+ result.contents = extractString(root, "dc:description");
+ result.creator = extractIdenFromRdfResource(root, "dc:creator");
+
+ return result;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcRelatedLink.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcRelatedLink.java
new file mode 100644
index 0000000..0016f3f
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcRelatedLink.java
@@ -0,0 +1,30 @@
+// 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.rtc.workitems;
+
+import java.net.URL;
+
+import com.googlesource.gerrit.plugins.hooks.rtc.api.RtcObject;
+
+public class RtcRelatedLink extends RtcObject {
+
+ public RtcRelatedLink(URL resourceUrl, String label2) {
+ this.resource = resourceUrl;
+ this.label = label2;
+ }
+
+ public URL resource;
+ public String label;
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcRelatedLinkDeserializer.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcRelatedLinkDeserializer.java
new file mode 100644
index 0000000..9f542d0
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcRelatedLinkDeserializer.java
@@ -0,0 +1,47 @@
+// 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.rtc.workitems;
+
+import java.lang.reflect.Type;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.googlesource.gerrit.plugins.hooks.rtc.api.AbstractDeserializer;
+
+public class RtcRelatedLinkDeserializer extends AbstractDeserializer
+ implements JsonDeserializer<RtcRelatedLink> {
+
+ @Override
+ public RtcRelatedLink deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) throws JsonParseException {
+
+ JsonObject jsonObj = json.getAsJsonObject();
+ String resourceUrlString = jsonObj.get("rdf:resource").getAsString();
+ try {
+ URL resourceUrl = new URL(resourceUrlString);
+ String label = jsonObj.get("oslc_cm:label").getAsString();
+
+ return new RtcRelatedLink(resourceUrl, label);
+
+ } catch (MalformedURLException e) {
+ throw new JsonParseException("Invalid rdf:resource URL '" + resourceUrlString + "'", e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcWorkItem.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcWorkItem.java
new file mode 100644
index 0000000..0963dc5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcWorkItem.java
@@ -0,0 +1,84 @@
+// 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.rtc.workitems;
+
+import java.util.Calendar;
+
+import com.googlesource.gerrit.plugins.hooks.rtc.api.RtcEntity;
+import com.googlesource.gerrit.plugins.hooks.rtc.api.RtcObject;
+
+
+public class RtcWorkItem extends RtcObject {
+
+ Long id;
+ String title;
+ String subject;
+ String creator;
+ String ownedby;
+ String description;
+ Calendar created;
+ RtcEntity status;
+ RtcEntity severity;
+ RtcEntity priority;
+ RtcEntity type;
+// private List<RtcComment> comments; //
+
+ RtcWorkItem(String rdf) {
+ super(rdf);
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public String getCreator() {
+ return creator;
+ }
+
+ public String getOwnedby() {
+ return ownedby;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public Calendar getCreated() {
+ return created;
+ }
+
+ public RtcEntity getStatus() {
+ return status;
+ }
+
+ public RtcEntity getSeverity() {
+ return severity;
+ }
+
+ public RtcEntity getPriority() {
+ return priority;
+ }
+
+ public RtcEntity getType() {
+ return type;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcWorkItemDeserializer.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcWorkItemDeserializer.java
new file mode 100644
index 0000000..c7123e2
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcWorkItemDeserializer.java
@@ -0,0 +1,67 @@
+// 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.rtc.workitems;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.googlesource.gerrit.plugins.hooks.rtc.api.AbstractDeserializer;
+import com.googlesource.gerrit.plugins.hooks.rtc.api.RtcEntity;
+import com.googlesource.gerrit.plugins.hooks.rtc.network.CachableResourcesFactory;
+
+public class RtcWorkItemDeserializer extends AbstractDeserializer implements JsonDeserializer<RtcWorkItem> {
+
+ private CachableResourcesFactory factory;
+
+ public RtcWorkItemDeserializer(CachableResourcesFactory factory) {
+ this.factory = factory;
+ }
+
+ @Override
+ public RtcWorkItem deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) throws JsonParseException {
+
+ JsonObject root = json.getAsJsonObject();
+
+ RtcWorkItem result = new RtcWorkItem(extractRdf(root));
+ result.id = extractLong(root, "dc:identifier");
+ result.title = extractString(root,"dc:title");
+ result.subject = extractString(root,"dc:subject");
+ result.creator = extractIdenFromRdfResource(root, "dc:creator");
+ result.ownedby = extractIdenFromRdfResource(root, "rtc_cm:ownedBy");
+ result.status = extractResource(root, "rtc_cm:state", RtcEntity.class);
+ result.severity = extractResource(root, "oslc_cm:severity", RtcEntity.class);
+ result.priority = extractResource(root, "oslc_cm:priority", RtcEntity.class);
+ result.type = extractResource(root, "dc:type", RtcEntity.class);
+
+
+ return result;
+ }
+
+ private RtcEntity extractResource(JsonObject root, final String memberName,
+ final Class<RtcEntity> clazz) {
+ String url = extractRdfResourceUrl(root, memberName);
+ try {
+ return factory.get(url, clazz);
+ } catch (IOException e) {
+ throw new JsonParseException("Unable to parse resource from url "+url+" using class "+clazz, e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcWorkflowAction.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcWorkflowAction.java
new file mode 100644
index 0000000..e322a3f
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcWorkflowAction.java
@@ -0,0 +1,32 @@
+// 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.rtc.workitems;
+
+import com.googlesource.gerrit.plugins.hooks.rtc.api.RtcEntity;
+
+public class RtcWorkflowAction extends RtcEntity {
+
+ String resultStateRdf;
+
+ public RtcWorkflowAction(String id, String title) {
+ super(id, title);
+ }
+ public RtcWorkflowAction(String rdf, String id, String title) {
+ super(rdf, id, title);
+ }
+
+ public String getResultStateRdf() {
+ return resultStateRdf;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcWorkflowActionDeserializer.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcWorkflowActionDeserializer.java
new file mode 100644
index 0000000..3fcc10f
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/RtcWorkflowActionDeserializer.java
@@ -0,0 +1,41 @@
+// 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.rtc.workitems;
+
+import java.lang.reflect.Type;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.googlesource.gerrit.plugins.hooks.rtc.api.AbstractDeserializer;
+
+public class RtcWorkflowActionDeserializer extends AbstractDeserializer implements JsonDeserializer<RtcWorkflowAction> {
+
+ @Override
+ public RtcWorkflowAction deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) throws JsonParseException {
+
+ JsonObject root = json.getAsJsonObject();
+
+ final String rdf = extractRdf(root);
+ final String id = extractString(root, "dc:identifier");
+ final String title = extractString(root, "dc:title");
+ RtcWorkflowAction result = new RtcWorkflowAction(rdf, id, title);
+ result.resultStateRdf = extractRdf(root.get("rtc_cm:resultState").getAsJsonObject());
+
+ return result;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/WorkItemsApi.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/WorkItemsApi.java
new file mode 100644
index 0000000..9706341
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/WorkItemsApi.java
@@ -0,0 +1,37 @@
+// 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.rtc.workitems;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+
+import com.googlesource.gerrit.plugins.hooks.rtc.api.RtcEntity;
+
+public interface WorkItemsApi {
+
+ public RtcWorkItem getWorkItem(long id) throws IOException;
+
+ public RtcComment addComment(long id, String text) throws IOException;
+
+ public RtcRelatedLink addRelated(long id, URL relatedUrl, String text)
+ throws IOException;
+
+ public List<RtcEntity> getAvailableStatuses(RtcWorkItem wip) throws IOException;
+
+ public List<RtcWorkflowAction> getAvailableActions(RtcWorkItem wip) throws IOException;
+
+ public RtcWorkItem performAction(RtcWorkItem wip, String newStatusTitle) throws IOException;
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/WorkItemsApiImpl.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/WorkItemsApiImpl.java
new file mode 100644
index 0000000..7e7af5b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/rtc/workitems/WorkItemsApiImpl.java
@@ -0,0 +1,141 @@
+// 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.rtc.workitems;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.net.URL;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.http.message.BasicNameValuePair;
+
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+import com.googlesource.gerrit.plugins.hooks.rtc.api.ResourceInvalidException;
+import com.googlesource.gerrit.plugins.hooks.rtc.api.RtcEntity;
+import com.googlesource.gerrit.plugins.hooks.rtc.network.RTCClient;
+import com.googlesource.gerrit.plugins.hooks.rtc.network.Transport;
+
+public class WorkItemsApiImpl extends AbstractApiImpl implements WorkItemsApi {
+
+ private Transport transport;
+ public WorkItemsApiImpl(RTCClient rtcClient, Transport transport) {
+ super(rtcClient);
+ this.transport = transport;
+ }
+
+ @Override
+ public synchronized RtcWorkItem getWorkItem(long id) throws IOException {
+ loginIfNeeded();
+ return transport.get("/oslc/workitems/" + id + ".json", RtcWorkItem.class);
+ }
+
+ @Override
+ public synchronized RtcComment addComment(long id, String text)
+ throws IOException {
+ loginIfNeeded();
+ return transport.post("/oslc/workitems/" + id + "/rtc_cm:comments",
+ RtcComment.class,
+ Transport.APP_JSON,
+ new BasicNameValuePair("dc:description", text));
+ }
+
+ @Override
+ public synchronized RtcRelatedLink addRelated(long id, URL relatedUrl,
+ String text) throws IOException {
+ loginIfNeeded();
+ return transport
+ .post(
+ "/oslc/workitems/"
+ + id
+ + "/rtc_cm:com.ibm.team.workitem.linktype.relatedartifact.relatedArtifact",
+ RtcRelatedLink.class,
+ Transport.APP_JSON,
+ new BasicNameValuePair("rdf:resource",
+ relatedUrl.toExternalForm()), new BasicNameValuePair(
+ "oslc_cm:label", text));
+ }
+
+ @Override
+ public List<RtcEntity> getAvailableStatuses(RtcWorkItem wip)
+ throws IOException {
+ loginIfNeeded();
+ final String rdf = wip.getStatus().getRdf();
+ final String url = rdf.substring(0, rdf.lastIndexOf('/')) + ".json";
+ final Type type = new TypeToken<Collection<RtcEntity>>() {}.getType();
+ return transport.get(url, type);
+ }
+
+ @Override
+ public List<RtcWorkflowAction> getAvailableActions(RtcWorkItem wip)
+ throws IOException {
+ loginIfNeeded();
+ final String rdf = wip.getStatus().getRdf();
+ final String url = rdf.substring(0, rdf.lastIndexOf('/')).replace("states", "actions")+ ".json";
+ final Type type = new TypeToken<Collection<RtcWorkflowAction>>() {}.getType();
+ return transport.get(url, type);
+ }
+
+ @Override
+ public RtcWorkItem performAction(RtcWorkItem wip, String actionTitle)
+ throws IOException {
+ loginIfNeeded();
+ RtcWorkflowAction action = null;
+ List<RtcWorkflowAction> allActions = getAvailableActions(wip);
+ for (RtcWorkflowAction anAction : allActions) {
+ if (anAction.getTitle().equalsIgnoreCase(actionTitle)) {
+ action = anAction;
+ break;
+ }
+ }
+
+ if (action == null) {
+ throw new ResourceInvalidException(actionTitle);
+ }
+
+ final String url = "/oslc/workitems/" + wip.getId()+"?_action="+action.getId();
+
+ JsonObject rdf = new JsonObject();
+ rdf.addProperty("rdf:resource", action.getResultStateRdf());
+ JsonObject data = new JsonObject();
+ data.add("rtc_cm:state", rdf);
+
+ return transport.patch(url, RtcWorkItem.class, data, wip.getEtag());
+ }
+
+/*
+ Object foo()
+ {
+// String s = wip.getStatus().getRdf();
+// JsonObject rdf = new JsonObject();
+// rdf.addProperty("rdf:resource", s);
+// JsonObject data = new JsonObject();
+// data.add("rtc_cm:state", rdf);
+
+ JsonObject rdf = new JsonObject();
+ final String url = newStatus.getRdf();
+ rdf.addProperty("rdf:resource", url);
+ JsonObject data = new JsonObject();
+ data.add("rtc_cm:state", rdf);
+
+// JsonObject data = new JsonObject();
+// data.addProperty("dc:title", "Hey, wombats! Is this so called 'patch' really working???");
+
+ System.err.println(data);
+ return transport.patch("/oslc/workitems/" + wip.getId(), RtcWorkItem.class, data, wip.getEtag());
+// return transport.put("/oslc/workitems/" + wip.getId()+"?oslc_cm.properties=rtc_cm:state", RtcWorkItem.class, data, wip.getEtag());
+ }
+*/
+}
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
new file mode 100644
index 0000000..0c0cece
--- /dev/null
+++ b/src/main/resources/Documentation/config.md
@@ -0,0 +1,211 @@
+Plugin @PLUGIN@
+===============
+
+This plugin allows to associate IBM Rational Team Concert (RTC) issues to Git commits / Gerrit
+Changes using the Gerrit ChangeListener and CommitValidator interfaces.
+
+Main integration points provided are:
+
+1. Commit validation (synchronous). It is the ability to introduce an additional validation
+step to the Git commits pushed to Gerrit either directly to the target branch or submitted
+for Code Review.
+2. Commit association and workflow (asynchronous). It is the bi-directional integration between
+the RTC issue and its corresponding Git commit / Gerrit Change. Additionally the workflow of
+the RTC issue and Gerrit Review can be linked together using an external actions mapping file.
+3. Configuration of comment links (init). It is an additional step included in the Gerrit init
+configuration that allows to configure the RTC issue syntax and formatting all the references
+rendered in Gerrit as hyperlinks to the corresponding issue target URL in RTC.
+
+Comment links
+----------------
+
+Git commits are associated to RTC issues reusing the existing Gerrit
+[commitLink configuration]i[1] to extract the issue ID from commit comments.
+
+[1]: ../../../Documentation/config-gerrit.html#_a_id_commentlink_a_section_commentlink
+
+Additionally you need to specify the enforcement policy for git commits
+with regards to issue-tracker associations; the following values are supported:
+
+`MANDATORY`
+: One or more issue-ids are required in the git commit message, otherwise
+ the git push will be rejected.
+ NOTE: triggers a *synchronous API call* to RTC for the issue-id lookup, push
+ is blocked until the operation is completed.
+
+`SUGGESTED`
+: Whenever git commit message does not contain one or more issue-ids,
+ a warning message is displayed as a suggestion on the client.
+ NOTE: triggers a *synchronous API call* to RTC for the issue-id lookup, push
+ is blocked until the operation is completed.
+
+`OPTIONAL`
+: Issues-ids are liked when found on git commit message, no warning are
+ displayed otherwise.
+
+**Example:**
+
+ [commentLink "RTC"]
+ match = RTC#([0-9]*)
+ html = "<a href=\"https://rtc.gerritforge.com:9443/ccm/browse/$1\">$1</a>"
+ association = OPTIONAL
+
+Once a Git commit with a comment link is detected, the RTC issue ID
+is extracted and a new comment added to the issue, pointing back to
+the original Git commit.
+
+RTC connectivity
+-----------------
+
+In order for Gerrit to connect to RTC REST-API, url and credentials
+are required in your gerrit.config / secure.config under the [rtc] section.
+
+**Example:**
+
+ [rtc]
+ url=https://rtc.gerritforge.com:9443/ccm
+ username=rtcuser
+ passsword=rtcpass
+
+RTC credentials and connectivity details are asked and verified during the Gerrit init.
+
+HTTP/S and network settings
+---------------------------
+
+There are no additional settings required for a default connectivity from Gerrit
+to RTC and the default JVM settings are automatically taken for opening outbound
+connections.
+
+However connectivity to RTC could be highly customised for defining the protocol
+security level, pooling and network settings. This allows the administrator
+to have full control of the output pipe to RTC and the propagation of the Change events
+to the associated issues in a high-loaded production environment.
+
+All settings are defined in gerrit.config under the same [rtc] section.
+See below the list of the most important parameters and their associated meaning.
+
+`sslVerify`
+: `[TRUE|FALSE]`. When using HTTP/S to connect to RTC (the most typical scenario)
+ allows to enforce (recommended) or disable (**ONLY FOR TEST ENVIRONMENTS**) the
+ X.509 Certificates validation during SSL handshake. If unsure say TRUE.
+
+`httpSocketTimeout`
+: `<number>` Defines the socket timeout in milliseconds,
+ which is the timeout for waiting for data or, put differently,
+ a maximum period inactivity between two consecutive data packets).
+ A timeout value of zero is interpreted as an infinite timeout.
+
+`httpSocketBufferSize`
+: `<number>` Determines the size of the internal socket buffer used to buffer data
+ while receiving / transmitting HTTP messages.
+
+`httpSocketReuseaddr`
+: `[TRUE|FALSE]` Defines whether the socket can be bound even though a previous connection is
+ still in a timeout state.
+
+`httpConnectionTimeout`
+: `<number>` Determines the timeout in milliseconds until a connection is established.
+ A timeout value of zero is interpreted as an infinite timeout.
+
+`httpConnectionStalecheck`
+: `[TRUE|FALSE]` Determines whether stale connection check is to be used. The stale
+ connection check can cause up to 30 millisecond overhead per request and
+ should be used only when appropriate. For performance critical
+ operations this check should be disabled.
+
+`httpSocketKeepalive`
+: `[TRUE|FALSE]` Defines whether or not TCP is to send automatically a keepalive probe to the peer
+ after an interval of inactivity (no data exchanged in either direction) between this
+ host and the peer. The purpose of this option is to detect if the peer host crashes.
+
+`httpConnManagerTimeout`
+: `<number>` Defines the timeout in milliseconds used when retrieving a free connection from the pool
+ of outbound HTTP connections allocated.
+
+`httpConnManagerMaxTotal`
+: `<number>` Defines the maximum number of outbound HTTP connections in total.
+ This limit is interpreted by client connection managers and applies to individual manager instances.
+
+**NOTE**: The full list of all available HTTP network connectivity parameters can be found under
+the [Apache Commons HTTP Client 4.2.x documentation](http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/index.html?org/apache/http/client/params/ClientPNames.html). Gerrit parameters names are the [CamelCase](http://en.wikipedia.org/wiki/Camelcase) version of the string
+values of the Apache HTTP Client ones.
+
+
+Gerrit init integration
+-----------------------
+
+RTC plugin is integrated as a Gerrit init step in order to simplify and guide
+through the configuration of RTC integration and connectivity check, avoiding
+bogus settings to prevent Gerrit plugin to start correctly.
+
+**Gerrit init example:**
+
+ *** IBM Rational Team Concert connectivity
+ ***
+
+ RTC CCM URL (empty to skip) : https://rtc.gerritforge.com:9443/ccm
+ RTC username []: gerrit
+ Change luca's password [y/N]? y
+ luca's password : ******
+ confirm password : ******
+ Verify SSL Certificates [TRUE/?]: false
+ Test connectivity to https://rtc.gerritforge.com:9443/ccm [N/?]: y
+ Checking IBM Rational Team Concert connectivity ... [OK]
+
+ *** Rational Team Concert issue-tracking association
+ ***
+
+ RTC issue-Id regex [RTC#([0-9]+)]:
+ RTC-Id enforced in commit message [OPTIONAL/?]: suggested
+
+Issues workflow automation
+--------------------------
+
+RTC plugin is able to automate status transition on the issues based on
+code-review actions performed on Gerrit; actions are performed on RTC using
+the username/password provided during Gerrit init.
+Transition automation is driven by `$GERRIT_SITE/issue-state-transition.config` file.
+
+Syntax of the status transition configuration file is the following:
+
+ [action "<issue-status-action>"]
+ change=<change-action>
+ verified=<verified-value>
+ code-review=<code-review-value>
+
+`<issue-status-action>`
+: Action to perform on RTC issue when all the condition in the stanza are met.
+
+`<change-action>`
+: Action performed on Gerrit change-id, possible values are:
+ `created, commented, merged, abandoned, restored`
+
+`<verified-value>`
+: Verified flag added on Gerrit with values from -1 to +1
+
+`<code-review-value>`
+: Code-Review flag added on Gerrit with values from -2 to +2
+
+Note: multiple conditions in the action stanza are optional but at least one must be present.
+
+Example:
+
+ [action "Start Progress"]
+ change=created
+
+ [action "Resolve Issue"]
+ verified=+1
+ code-review=+2
+
+ [action "Close Issue"]
+ change=merged
+
+ [action "Stop Progress"]
+ change=abandoned
+
+The above example defines four status transition on RTC, based on the following conditions:
+
+* Whenever a new Change-set is created on Gerrit, start progress on the RTC issue
+* Whenever a change is verified and reviewed with +2, transition the RTC issue to resolved
+* Whenever a change is merged to branch, mark the RTC transition the RTC issue to closed
+* Whenever a change is abandoned, stop the progress on RTC issue
\ No newline at end of file
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/rtc/ChangeListenerAsyncDecoratorTest.java b/src/test/java/com/googlesource/gerrit/plugins/hooks/rtc/ChangeListenerAsyncDecoratorTest.java
new file mode 100644
index 0000000..764f5f5
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/hooks/rtc/ChangeListenerAsyncDecoratorTest.java
@@ -0,0 +1,134 @@
+// 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.rtc;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+
+import com.google.gerrit.common.ChangeListener;
+import com.google.gerrit.server.events.ChangeEvent;
+import com.googlesource.gerrit.plugins.hooks.rtc.filters.ChangeListenerAsyncDecorator;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ChangeListenerAsyncDecoratorTest {
+
+ @Mock
+ ChangeListener listener;
+ @Mock
+ ChangeEvent event;
+ @Mock
+ ExecutorService executor;
+ @Mock
+ ExecutorService immediateExecutor;
+
+ ChangeListenerAsyncDecorator<ChangeListener> asyncListener;
+
+ @Before
+ public void setUp() {
+ when(immediateExecutor.submit(any(Runnable.class))).thenAnswer(
+ new Answer<Future<?>>() {
+
+ @Override
+ public Future<?> answer(InvocationOnMock invocation) throws Throwable {
+ Runnable task = (Runnable) invocation.getArguments()[0];
+ task.run();
+ return null;
+
+ }
+ });
+ asyncListener =
+ new ChangeListenerAsyncDecorator<ChangeListener>(listener, executor);
+ }
+
+ @Test
+ public void testQueueShouldBeEmptyWhenCreated() {
+ assertTrue(asyncListener.getQueue().isEmpty());
+ }
+
+ @Test
+ public void testQueueShouldNotBeEmptyWhenOneEventSubmitted() {
+ asyncListener.onChangeEvent(event);
+ assertFalse(asyncListener.getQueue().isEmpty());
+ }
+
+ @Test
+ public void testChangeEventShouldBeQueuedWhenSubmitted() {
+ asyncListener.onChangeEvent(event);
+ assertEquals(event, asyncListener.getQueue().peek());
+ }
+
+ @Test
+ public void testChangeEventShouldBeSentToExecutor() {
+ asyncListener.onChangeEvent(event);
+ verify(executor).submit(
+ any(ChangeListenerAsyncDecorator.ChangeRunner.class));
+ }
+
+ @Test
+ public void testChangeEventShouldBePropagatedToListenerWhenImmediatelyExecuted() {
+ asyncListener =
+ new ChangeListenerAsyncDecorator<ChangeListener>(listener,
+ immediateExecutor);
+ asyncListener.onChangeEvent(event);
+ verify(listener).onChangeEvent(event);
+ assertTrue(asyncListener.getQueue().isEmpty());
+ }
+
+ @Test
+ public void testChangeEventShouldStayInQueueWhenExecutionFailed() {
+ asyncListener =
+ new ChangeListenerAsyncDecorator<ChangeListener>(listener,
+ immediateExecutor);
+ doThrow(new IllegalArgumentException()).when(listener).onChangeEvent(
+ any(ChangeEvent.class));
+
+ asyncListener.onChangeEvent(event);
+ verify(listener).onChangeEvent(event);
+ assertFalse(asyncListener.getQueue().isEmpty());
+ }
+
+ @Test
+ public void testChangeShouldProcessAllPreviouslyFailedEventsInQueue() {
+ asyncListener =
+ new ChangeListenerAsyncDecorator<ChangeListener>(listener,
+ immediateExecutor);
+
+ doThrow(new IllegalArgumentException()).when(listener).onChangeEvent(
+ any(ChangeEvent.class));
+ asyncListener.onChangeEvent(event);
+ verify(listener).onChangeEvent(event);
+ assertFalse(asyncListener.getQueue().isEmpty());
+
+ doNothing().when(listener).onChangeEvent(any(ChangeEvent.class));
+ asyncListener.onChangeEvent(event);
+ verify(listener, times(3)).onChangeEvent(event);
+
+ assertTrue(asyncListener.getQueue().isEmpty());
+ }
+
+}