Merge branch 'stable-2.11' into 'master'

No commits included, merge executed only for release-management purposes
and applying future fixes on both stable-2.11 and master.

Change-Id: I0fe480719f3f9083d44bfaa01dbd9ef43591ccca
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index 2a585e4..258f774 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -1,12 +1,20 @@
 eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
 org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
 org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
 org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
 org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
 org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
 org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
 org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
 org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=ignore
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
 org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
 org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
 org.eclipse.jdt.core.compiler.problem.deadCode=warning
@@ -14,7 +22,8 @@
 org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
 org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
 org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
-org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
 org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
 org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
 org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
@@ -33,13 +42,14 @@
 org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
 org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
 org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning
 org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
 org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
 org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
 org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
 org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
 org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
 org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
 org.eclipse.jdt.core.compiler.problem.nullReference=warning
 org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
@@ -52,7 +62,7 @@
 org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
 org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
 org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning
 org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
 org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
 org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
@@ -60,6 +70,7 @@
 org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
 org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
 org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
 org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
 org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
 org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
@@ -70,7 +81,7 @@
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
@@ -83,6 +94,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
 org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
 org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
 org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
 org.eclipse.jdt.core.compiler.source=1.7
diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs
index d4218a5..7397758 100644
--- a/.settings/org.eclipse.jdt.ui.prefs
+++ b/.settings/org.eclipse.jdt.ui.prefs
@@ -1,10 +1,9 @@
-#Wed Jul 29 11:31:38 PDT 2009
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_Google Format
 formatter_settings_version=11
 org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
+org.eclipse.jdt.ui.importorder=\#;com.google;com;dk;eu;junit;net;org;java;javax;
 org.eclipse.jdt.ui.ondemandthreshold=99
 org.eclipse.jdt.ui.staticondemandthreshold=99
 org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
diff --git a/BUCK b/BUCK
index e8cbb9a..a93fedd 100644
--- a/BUCK
+++ b/BUCK
@@ -1,41 +1,29 @@
-include_defs('//bucklets/gerrit_plugin.bucklet')
+# This plugin currently does not support a standalone build, as a
+# standalone build was deemed too much maintenance overhead in its
+# present (2015-09-20) form.
+#
+# Once the standalone build does no longer come with a maintenance
+# overhead, a first shot at the standalone build for this plugin can
+# be found at:
+#
+#   https://gerrit-review.googlesource.com/#/c/70896/
+#
 
-DEPS = [
-  '//lib/commons:lang',
-  '//lib:guava',
-  '//lib/guice:guice',
-  '//lib/jgit:jgit',
-  '//lib/log:api',
-  '//lib:grappa', # used for it's parboiled part. See
-  # core's 4e69765b9e1acb1dd953dea99dd0ec532dee95e4
-  # and 0db7612e092b37c6ea04883a5a45f51c6d9ae433
-  # for details.
-  '//lib:velocity',
-]
+include_defs('//bucklets/gerrit_plugin.bucklet')
 
 gerrit_plugin(
   name = 'its-base',
   srcs = glob(['src/main/java/**/*.java']),
   resources = glob(['src/main/resources/**/*']),
-  provided_deps = DEPS,
 )
 
-TEST_UTIL_SRC = glob(['src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/**/*.java'])
+TEST_UTIL_SRC = glob(['src/test/java/com/googlesource/gerrit/plugins/its/base/testutil/**/*.java'])
 
 java_library(
   name = 'its-base_tests-utils',
   srcs = TEST_UTIL_SRC,
-  deps = DEPS + [
-    '//lib/easymock:easymock',
-    '//lib/log:impl_log4j',
-    '//lib/log:log4j',
-    '//lib:junit',
-    '//lib/powermock:powermock-api-easymock',
-    '//lib/powermock:powermock-api-support',
-    '//lib/powermock:powermock-core',
-    '//lib/powermock:powermock-module-junit4',
-    '//lib/powermock:powermock-module-junit4-common',
-  ],
+  deps = GERRIT_PLUGIN_API + GERRIT_TESTS,
+  visibility = ['PUBLIC'],
 )
 
 java_test(
@@ -46,19 +34,8 @@
   ),
   labels = ['its-base'],
   source_under_test = [':its-base__plugin'],
-  deps = DEPS + [
+  deps = GERRIT_PLUGIN_API + GERRIT_TESTS + [
     ':its-base__plugin',
     ':its-base_tests-utils',
-    '//gerrit-plugin-api:lib',
-    '//lib:gwtorm',
-    '//lib/easymock:easymock',
-    '//lib/log:impl_log4j',
-    '//lib/log:log4j',
-    '//lib:junit',
-    '//lib/powermock:powermock-api-easymock',
-    '//lib/powermock:powermock-api-support',
-    '//lib/powermock:powermock-core',
-    '//lib/powermock:powermock-module-junit4',
-    '//lib/powermock:powermock-module-junit4-common',
   ],
 )
diff --git a/pom.xml b/pom.xml
deleted file mode 100644
index e91a789..0000000
--- a/pom.xml
+++ /dev/null
@@ -1,106 +0,0 @@
-<?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>
-  <groupId>com.googlesource.gerrit.plugins.its</groupId>
-  <artifactId>its-base</artifactId>
-  <packaging>jar</packaging>
-  <version>2.11.5</version>
-  <name>Gerrit Code Review - Issue tracker support</name>
-
-  <properties>
-    <easymockVersion>3.0</easymockVersion>
-    <powermockVersion>1.5</powermockVersion>
-    <slf4jVersion>1.6.2</slf4jVersion>
-    <velocityVersion>1.6.4</velocityVersion>
-    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-  </properties>
-
-  <build>
-    <plugins>
-      <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-compiler-plugin</artifactId>
-        <version>3.1</version>
-        <configuration>
-          <source>1.7</source>
-          <target>1.7</target>
-          <encoding>UTF-8</encoding>
-        </configuration>
-      </plugin>
-        <plugin>
-          <groupId>org.apache.maven.plugins</groupId>
-          <artifactId>maven-shade-plugin</artifactId>
-          <version>1.6</version>
-        </plugin>
-    </plugins>
-  </build>
-
-  <dependencies>
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-plugin-api</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.velocity</groupId>
-      <artifactId>velocity</artifactId>
-      <version>${velocityVersion}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-log4j12</artifactId>
-      <version>${slf4jVersion}</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.easymock</groupId>
-      <artifactId>easymock</artifactId>
-      <version>${easymockVersion}</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.powermock</groupId>
-      <artifactId>powermock-module-junit4</artifactId>
-      <version>${powermockVersion}</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.powermock</groupId>
-      <artifactId>powermock-api-easymock</artifactId>
-      <version>${powermockVersion}</version>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-
-  <repositories>
-    <repository>
-      <id>gerrit-api-repository</id>
-      <url>https://gerrit-api.commondatastorage.googleapis.com/release/</url>
-    </repository>
-  </repositories>
-
-</project>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/ItsHookModule.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/ItsHookModule.java
deleted file mode 100644
index 2504937..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/ItsHookModule.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.hooks;
-
-import com.google.gerrit.common.EventListener;
-import com.google.gerrit.extensions.annotations.Exports;
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.server.config.FactoryModule;
-import com.google.gerrit.server.config.PluginConfigFactory;
-import com.google.gerrit.server.config.ProjectConfigEntry;
-import com.google.gerrit.server.git.validators.CommitValidationListener;
-
-import com.googlesource.gerrit.plugins.hooks.its.ItsConfig;
-import com.googlesource.gerrit.plugins.hooks.its.ItsHookEnabledConfigEntry;
-import com.googlesource.gerrit.plugins.hooks.validation.ItsValidateComment;
-import com.googlesource.gerrit.plugins.hooks.workflow.ActionController;
-import com.googlesource.gerrit.plugins.hooks.workflow.ActionRequest;
-import com.googlesource.gerrit.plugins.hooks.workflow.Condition;
-import com.googlesource.gerrit.plugins.hooks.workflow.GerritHookFilterAddComment;
-import com.googlesource.gerrit.plugins.hooks.workflow.GerritHookFilterAddRelatedLinkToChangeId;
-import com.googlesource.gerrit.plugins.hooks.workflow.GerritHookFilterAddRelatedLinkToGitWeb;
-import com.googlesource.gerrit.plugins.hooks.workflow.GerritHookFilterChangeState;
-import com.googlesource.gerrit.plugins.hooks.workflow.Property;
-import com.googlesource.gerrit.plugins.hooks.workflow.Rule;
-import com.googlesource.gerrit.plugins.hooks.workflow.action.AddComment;
-import com.googlesource.gerrit.plugins.hooks.workflow.action.AddStandardComment;
-import com.googlesource.gerrit.plugins.hooks.workflow.action.AddVelocityComment;
-import com.googlesource.gerrit.plugins.hooks.workflow.action.LogEvent;
-
-public class ItsHookModule extends FactoryModule {
-
-  private final String pluginName;
-  private final PluginConfigFactory pluginCfgFactory;
-
-  public ItsHookModule(@PluginName String pluginName,
-      PluginConfigFactory pluginCfgFactory) {
-    this.pluginName = pluginName;
-    this.pluginCfgFactory = pluginCfgFactory;
-  }
-
-  @Override
-  protected void configure() {
-    bind(ProjectConfigEntry.class)
-        .annotatedWith(Exports.named("enabled"))
-        .toInstance(new ItsHookEnabledConfigEntry(pluginName, pluginCfgFactory));
-    bind(ItsConfig.class);
-    DynamicSet.bind(binder(), EventListener.class).to(
-        GerritHookFilterAddRelatedLinkToChangeId.class);
-    DynamicSet.bind(binder(), EventListener.class).to(
-        GerritHookFilterAddComment.class);
-    DynamicSet.bind(binder(), EventListener.class).to(
-        GerritHookFilterChangeState.class);
-    DynamicSet.bind(binder(), EventListener.class).to(
-        GerritHookFilterAddRelatedLinkToGitWeb.class);
-    DynamicSet.bind(binder(), CommitValidationListener.class).to(
-        ItsValidateComment.class);
-    DynamicSet.bind(binder(), EventListener.class).to(
-        ActionController.class);
-    factory(ActionRequest.Factory.class);
-    factory(Property.Factory.class);
-    factory(Condition.Factory.class);
-    factory(Rule.Factory.class);
-    factory(AddComment.Factory.class);
-    factory(AddStandardComment.Factory.class);
-    factory(AddVelocityComment.Factory.class);
-    factory(LogEvent.Factory.class);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsConfig.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsConfig.java
deleted file mode 100644
index 83ed535..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsConfig.java
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.hooks.its;
-
-import com.google.gerrit.common.data.RefConfigSection;
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.PluginConfig;
-import com.google.gerrit.server.config.PluginConfigFactory;
-import com.google.gerrit.server.events.ChangeAbandonedEvent;
-import com.google.gerrit.server.events.ChangeMergedEvent;
-import com.google.gerrit.server.events.ChangeRestoredEvent;
-import com.google.gerrit.server.events.CommentAddedEvent;
-import com.google.gerrit.server.events.Event;
-import com.google.gerrit.server.events.PatchSetCreatedEvent;
-import com.google.gerrit.server.events.RefUpdatedEvent;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.project.RefPatternMatcher;
-import com.google.inject.Inject;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class ItsConfig {
-  private static final Logger log = LoggerFactory.getLogger(ItsConfig.class);
-
-  private final String pluginName;
-  private final ProjectCache projectCache;
-  private final PluginConfigFactory pluginCfgFactory;
-
-  @Inject
-  public ItsConfig(@PluginName String pluginName, ProjectCache projectCache,
-      PluginConfigFactory pluginCfgFactory) {
-    this.pluginName = pluginName;
-    this.projectCache = projectCache;
-    this.pluginCfgFactory = pluginCfgFactory;
-  }
-
-  public boolean isEnabled(Event event) {
-    if (event instanceof PatchSetCreatedEvent) {
-      PatchSetCreatedEvent e = (PatchSetCreatedEvent) event;
-      return isEnabled(e.change.project, e.change.branch);
-    } else if (event instanceof CommentAddedEvent) {
-      CommentAddedEvent e = (CommentAddedEvent) event;
-      return isEnabled(e.change.project, e.change.branch);
-    } else if (event instanceof ChangeMergedEvent) {
-      ChangeMergedEvent e = (ChangeMergedEvent) event;
-      return isEnabled(e.change.project, e.change.branch);
-    } else if (event instanceof ChangeAbandonedEvent) {
-      ChangeAbandonedEvent e = (ChangeAbandonedEvent) event;
-      return isEnabled(e.change.project, e.change.branch);
-    } else if (event instanceof ChangeRestoredEvent) {
-      ChangeRestoredEvent e = (ChangeRestoredEvent) event;
-      return isEnabled(e.change.project, e.change.branch);
-    } else if (event instanceof RefUpdatedEvent) {
-      RefUpdatedEvent e = (RefUpdatedEvent) event;
-      return isEnabled(e.refUpdate.project, e.refUpdate.refName);
-    } else {
-      log.debug("Event " + event + " not recognised and ignored");
-      return false;
-    }
-  }
-
-  public boolean isEnabled(String project, String branch) {
-    ProjectState projectState = projectCache.get(new Project.NameKey(project));
-    if (projectState == null) {
-      log.error("Failed to check if " + pluginName + " is enabled for project "
-          + project + ": Project " + project + " not found");
-      return false;
-    }
-
-    for (ProjectState parentState : projectState.treeInOrder()) {
-      PluginConfig parentCfg =
-          pluginCfgFactory.getFromProjectConfig(parentState, pluginName);
-      if ("enforced".equals(parentCfg.getString("enabled"))
-          && isEnabledForBranch(parentState, branch)) {
-        return true;
-      }
-    }
-
-    return pluginCfgFactory.getFromProjectConfigWithInheritance(
-        projectState, pluginName).getBoolean("enabled", false)
-        && isEnabledForBranch(projectState, branch);
-  }
-
-  private boolean isEnabledForBranch(ProjectState project, String branch) {
-    String[] refPatterns =
-        pluginCfgFactory.getFromProjectConfigWithInheritance(project,
-            pluginName).getStringList("branch");
-    if (refPatterns.length == 0) {
-      return true;
-    }
-    for (String refPattern : refPatterns) {
-      if (RefConfigSection.isValid(refPattern) && match(branch, refPattern)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private boolean match(String branch, String refPattern) {
-    return RefPatternMatcher.getMatcher(refPattern).match(branch, null);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/GerritHookFilter.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/GerritHookFilter.java
deleted file mode 100644
index da062bc..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/GerritHookFilter.java
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.hooks.workflow;
-
-import com.google.gerrit.common.EventListener;
-import com.google.gerrit.server.events.ChangeAbandonedEvent;
-import com.google.gerrit.server.events.ChangeMergedEvent;
-import com.google.gerrit.server.events.ChangeRestoredEvent;
-import com.google.gerrit.server.events.CommentAddedEvent;
-import com.google.gerrit.server.events.Event;
-import com.google.gerrit.server.events.PatchSetCreatedEvent;
-import com.google.gerrit.server.events.RefUpdatedEvent;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-
-import com.googlesource.gerrit.plugins.hooks.its.ItsConfig;
-import com.googlesource.gerrit.plugins.hooks.util.CommitMessageFetcher;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-public class GerritHookFilter implements EventListener {
-  private static final Logger log = LoggerFactory.getLogger(GerritHookFilter.class);
-
-  @Inject
-  private CommitMessageFetcher commitMessageFetcher;
-
-  @Inject
-  private ItsConfig itsConfig;
-
-  public String getComment(String projectName, String commitId)
-      throws IOException {
-    return commitMessageFetcher.fetch(projectName, commitId);
-  }
-
-  public void doFilter(PatchSetCreatedEvent hook) throws IOException,
-      OrmException {
-  }
-
-  public void doFilter(CommentAddedEvent hook) throws IOException {
-  }
-
-  public void doFilter(ChangeMergedEvent hook) throws IOException {
-  }
-
-  public void doFilter(ChangeAbandonedEvent changeAbandonedHook)
-      throws IOException {
-  }
-
-  public void doFilter(ChangeRestoredEvent changeRestoredHook)
-      throws IOException {
-  }
-
-  public void doFilter(RefUpdatedEvent refUpdatedHook) throws IOException {
-  }
-
-  @Override
-  public void onEvent(Event event) {
-    if (!itsConfig.isEnabled(event)) {
-      return;
-    }
-
-    try {
-      if (event instanceof PatchSetCreatedEvent) {
-        doFilter((PatchSetCreatedEvent) event);
-      } else if (event instanceof CommentAddedEvent) {
-        doFilter((CommentAddedEvent) event);
-      } else if (event instanceof ChangeMergedEvent) {
-        doFilter((ChangeMergedEvent) event);
-      } else if (event instanceof ChangeAbandonedEvent) {
-        doFilter((ChangeAbandonedEvent) event);
-      } else if (event instanceof ChangeRestoredEvent) {
-        doFilter((ChangeRestoredEvent) event);
-      } else if (event instanceof RefUpdatedEvent) {
-        doFilter((RefUpdatedEvent) event);
-      } else {
-        log.debug("Event " + event + " not recognised and ignored");
-      }
-    } catch (Throwable e) {
-      log.error("Event " + event + " processing failed", e);
-    }
-  }
-
-  public String getUrl(PatchSetCreatedEvent hook) {
-    return null;
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/GerritHookFilterAddComment.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/GerritHookFilterAddComment.java
deleted file mode 100644
index 123f119..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/GerritHookFilterAddComment.java
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.hooks.workflow;
-
-import java.io.IOException;
-
-import org.eclipse.jgit.lib.Config;
-
-import com.google.common.base.Strings;
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.server.config.AnonymousCowardName;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.data.AccountAttribute;
-import com.google.gerrit.server.data.ApprovalAttribute;
-import com.google.gerrit.server.data.ChangeAttribute;
-import com.google.gerrit.server.events.ChangeAbandonedEvent;
-import com.google.gerrit.server.events.ChangeEvent;
-import com.google.gerrit.server.events.ChangeMergedEvent;
-import com.google.gerrit.server.events.ChangeRestoredEvent;
-import com.google.gerrit.server.events.CommentAddedEvent;
-import com.google.inject.Inject;
-
-import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-import com.googlesource.gerrit.plugins.hooks.util.IssueExtractor;
-
-public class GerritHookFilterAddComment extends GerritHookFilter  {
-
-  @Inject
-  private ItsFacade its;
-
-  @Inject @AnonymousCowardName
-  private String anonymousCowardName;
-
-  @Inject
-  @GerritServerConfig
-  private Config gerritConfig;
-
-  @Inject
-  private IssueExtractor issueExtractor;
-
-  @Inject @PluginName
-  private String pluginName;
-
-  @Override
-  public void doFilter(CommentAddedEvent hook) throws IOException {
-    if (!(gerritConfig.getBoolean(pluginName, null, "commentOnCommentAdded",
-        true))) {
-      return;
-    }
-
-    String comment = getComment(hook);
-    addComment(hook.change, comment);
-  }
-
-  @Override
-  public void doFilter(ChangeMergedEvent hook) throws IOException {
-    if (!(gerritConfig.getBoolean(pluginName, null, "commentOnChangeMerged",
-        true))) {
-      return;
-    }
-
-    String comment = getComment(hook);
-    addComment(hook.change, comment);
-  }
-
-  @Override
-  public void doFilter(ChangeAbandonedEvent hook) throws IOException {
-    if (!(gerritConfig.getBoolean(pluginName, null, "commentOnChangeAbandoned",
-        true))) {
-      return;
-    }
-    String comment = getComment(hook);
-    addComment(hook.change, comment);
-  }
-
-  @Override
-  public void doFilter(ChangeRestoredEvent hook) throws IOException {
-    if (!(gerritConfig.getBoolean(pluginName, null, "commentOnChangeRestored",
-        true))) {
-      return;
-    }
-    String comment = getComment(hook);
-    addComment(hook.change, comment);
-  }
-
-  private String getCommentPrefix(ChangeAttribute change) {
-    return getChangeIdUrl(change) + " | ";
-  }
-
-  private String formatAccountAttribute(AccountAttribute who) {
-    if (who != null && !Strings.isNullOrEmpty(who.name)) {
-      return who.name;
-    }
-    return anonymousCowardName;
-  }
-
-  private String getComment(ChangeAttribute change, ChangeEvent hook, AccountAttribute who, String what) {
-    return getCommentPrefix(change) + "change " + what + " [by "
-        + formatAccountAttribute(who) + "]";
-  }
-
-  private String getComment(ChangeRestoredEvent hook) {
-    return getComment(hook.change, hook, hook.restorer, "RESTORED");
-  }
-
-  private String getComment(ChangeAbandonedEvent hook) {
-    return getComment(hook.change, hook, hook.abandoner, "ABANDONED");
-  }
-
-  private String getComment(ChangeMergedEvent hook) {
-    return getComment(hook.change, hook, hook.submitter, "APPROVED and MERGED");
-  }
-
-  private String getChangeIdUrl(ChangeAttribute change) {
-    final String url = change.url;
-    String changeId = change.id;
-    return its.createLinkForWebui(url, "Gerrit Change " + changeId);
-  }
-
-  private String getComment(CommentAddedEvent commentAdded) {
-    StringBuilder comment = new StringBuilder(getCommentPrefix(commentAdded.change));
-
-    if (commentAdded.approvals != null && commentAdded.approvals.length > 0) {
-      comment.append("Code-Review: ");
-      for (ApprovalAttribute approval : commentAdded.approvals) {
-        String value = getApprovalValue(approval);
-        if (value != null) {
-          comment.append(getApprovalType(approval) + ":" + value + " ");
-        }
-      }
-    }
-
-    comment.append(commentAdded.comment + " ");
-    comment.append("[by " + formatAccountAttribute(commentAdded.author) + "]");
-    return comment.toString();
-  }
-
-  private String getApprovalValue(ApprovalAttribute approval) {
-    if (approval.value.equals("0")) {
-      return null;
-    }
-
-    if (approval.value.charAt(0) != '-') {
-      return "+" + approval.value;
-    } else {
-      return approval.value;
-    }
-  }
-
-  private String getApprovalType(ApprovalAttribute approval) {
-    if (approval.type.equalsIgnoreCase("CRVW")) {
-      return "Reviewed";
-    } else if (approval.type.equalsIgnoreCase("VRIF")) {
-      return "Verified";
-    } else
-      return approval.type;
-  }
-
-  private void addComment(ChangeAttribute change, String comment)
-      throws IOException {
-    String gitComment = change.subject;;
-    String[] issues = issueExtractor.getIssueIds(gitComment);
-
-    for (String issue : issues) {
-      its.addComment(issue, comment);
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/GerritHookFilterAddRelatedLinkToChangeId.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/GerritHookFilterAddRelatedLinkToChangeId.java
deleted file mode 100644
index 8d6b4ca..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/GerritHookFilterAddRelatedLinkToChangeId.java
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.hooks.workflow;
-
-import java.io.IOException;
-import java.net.URL;
-import java.util.Iterator;
-import java.util.List;
-
-import org.eclipse.jgit.lib.Config;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.collect.Lists;
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.events.PatchSetCreatedEvent;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Inject;
-
-import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-import com.googlesource.gerrit.plugins.hooks.util.IssueExtractor;
-
-public class GerritHookFilterAddRelatedLinkToChangeId extends
-    GerritHookFilter  {
-
-  Logger log = LoggerFactory
-      .getLogger(GerritHookFilterAddRelatedLinkToChangeId.class);
-
-  @Inject
-  private ItsFacade its;
-
-  @Inject
-  @GerritServerConfig
-  private Config gerritConfig;
-
-  @Inject
-  private IssueExtractor issueExtractor;
-
-  @Inject
-  private ReviewDb db;
-
-  @Inject @PluginName
-  private String pluginName;
-
-
-  /**
-   * Filter issues to those that occur for the first time in a change
-   *
-   * @param issues The issues to filter.
-   * @param patchSet Filter for this patch set.
-   * @return the issues that occur for the first time.
-   * @throws IOException
-   * @throws OrmException
-   */
-  private List<String> filterForFirstLinkedIssues(String[] issues,
-      PatchSetCreatedEvent patchSet) throws IOException, OrmException {
-    List<String> ret = Lists.newArrayList(issues);
-    int patchSetNumberCurrent = Integer.parseInt(patchSet.patchSet.number);
-
-    if (patchSetNumberCurrent > 1) {
-      String project = patchSet.change.project;
-      int changeNumber = Integer.parseInt(patchSet.change.number);
-      Change.Id changeId = new Change.Id(changeNumber);
-
-      // It would be nice to get patch sets directly via
-      //   patchSetCreated.change.patchSets
-      // but it turns out that it's null for our events. So we fetch the patch
-      // sets from the db instead.
-      ResultSet<PatchSet> patchSets = db.patchSets().byChange(changeId);
-      Iterator<PatchSet> patchSetIter = patchSets.iterator();
-
-      while (!ret.isEmpty() && patchSetIter.hasNext()) {
-        PatchSet previousPatchSet = patchSetIter.next();
-        if (previousPatchSet.getPatchSetId() < patchSetNumberCurrent) {
-          String commitMessage = getComment(project,
-              previousPatchSet.getRevision().get());
-          for (String issue : issueExtractor.getIssueIds(commitMessage)) {
-            ret.remove(issue);
-          }
-        }
-      }
-    }
-    return ret;
-  }
-
-  @Override
-  public void doFilter(PatchSetCreatedEvent patchsetCreated)
-      throws IOException, OrmException {
-    boolean addPatchSetComment = gerritConfig.getBoolean(pluginName, null,
-        "commentOnPatchSetCreated", true);
-
-    boolean addChangeComment = "1".equals(patchsetCreated.patchSet.number) &&
-        gerritConfig.getBoolean(pluginName, null, "commentOnChangeCreated",
-            false);
-
-    boolean addFirstLinkedPatchSetComment = gerritConfig.getBoolean(pluginName,
-        null, "commentOnFirstLinkedPatchSetCreated", false);
-
-    if (addPatchSetComment || addFirstLinkedPatchSetComment || addChangeComment) {
-      String gitComment =
-          getComment(patchsetCreated.change.project,
-              patchsetCreated.patchSet.revision);
-
-      String[] issues = issueExtractor.getIssueIds(gitComment);
-
-      List<String> firstLinkedIssues = null;
-      if (addFirstLinkedPatchSetComment) {
-        firstLinkedIssues = filterForFirstLinkedIssues(issues, patchsetCreated);
-      }
-
-      for (String issue : issues) {
-        if (addChangeComment) {
-          its.addRelatedLink(issue, new URL(patchsetCreated.change.url),
-              "Gerrit Change " + patchsetCreated.change.id);
-        }
-
-        if (addPatchSetComment) {
-          its.addRelatedLink(issue, new URL(patchsetCreated.change.url),
-              "Gerrit Patch-Set " + patchsetCreated.change.id + "/"
-                  + patchsetCreated.patchSet.number);
-        }
-
-        if (addFirstLinkedPatchSetComment && firstLinkedIssues.contains(issue)) {
-          its.addRelatedLink(issue, new URL(patchsetCreated.change.url),
-              "Gerrit Patch-Set " + patchsetCreated.change.id + "/"
-                  + patchsetCreated.patchSet.number);
-        }
-      }
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/GerritHookFilterAddRelatedLinkToGitWeb.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/GerritHookFilterAddRelatedLinkToGitWeb.java
deleted file mode 100644
index cee6d79..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/GerritHookFilterAddRelatedLinkToGitWeb.java
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.hooks.workflow;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.eclipse.jgit.lib.Config;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.gerrit.common.data.GitWebType;
-import com.google.gerrit.common.data.ParameterizedString;
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.httpd.GitWebConfig;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.events.RefUpdatedEvent;
-import com.google.inject.Inject;
-
-import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-import com.googlesource.gerrit.plugins.hooks.util.IssueExtractor;
-
-public class GerritHookFilterAddRelatedLinkToGitWeb extends GerritHookFilter {
-
-  Logger log = LoggerFactory
-      .getLogger(GerritHookFilterAddRelatedLinkToGitWeb.class);
-
-  @Inject
-  @GerritServerConfig
-  private Config gerritConfig;
-
-  @Inject
-  private ItsFacade its;
-
-  @Inject
-  private GitWebConfig gitWebConfig;
-
-  @Inject
-  private IssueExtractor issueExtractor;
-
-  @Inject @PluginName
-  private String pluginName;
-
-  @Override
-  public void doFilter(RefUpdatedEvent hook) throws IOException {
-    if (!(gerritConfig.getBoolean(pluginName, null, "commentOnRefUpdatedGitWeb",
-        true))) {
-      return;
-    }
-
-    String gitComment = getComment(hook.refUpdate.project,  hook.refUpdate.newRev);
-    log.debug("Git commit " + hook.refUpdate.newRev + ": " + gitComment);
-
-    URL gitUrl = getGitUrl(hook);
-    if (gitUrl == null) {
-      return;
-    }
-    String[] issues = issueExtractor.getIssueIds(gitComment);
-
-    for (String issue : issues) {
-      log.debug("Adding GitWeb URL " + gitUrl + " to issue " + issue);
-
-      its.addRelatedLink(issue, gitUrl, "Git: "
-          + hook.refUpdate.newRev);
-    }
-  }
-
-
-  /**
-   * generates the URL to GitWeb for the event
-   *
-   * @return if null is returned, the configuration does not allow to come up
-   * with a GitWeb url. In that case, a message describing the problematic
-   * setting has been logged.
-   */
-  private URL getGitUrl(RefUpdatedEvent hook) throws MalformedURLException,
-      UnsupportedEncodingException {
-    String gerritCanonicalUrl =
-        gerritConfig.getString("gerrit", null, "canonicalWebUrl");
-    if (gerritCanonicalUrl == null) {
-      log.info( "No canonicalWebUrl configured. Skipping GitWeb link generation");
-      return null;
-    }
-    if(!gerritCanonicalUrl.endsWith("/")) {
-      gerritCanonicalUrl += "/";
-    }
-
-    String gitWebUrl = gitWebConfig.getUrl();
-    if (gitWebUrl == null) {
-      log.info( "No url for GitWeb found. Skipping GitWeb link generation");
-      return null;
-    }
-    if (!gitWebUrl.startsWith("http")) {
-      gitWebUrl = gerritCanonicalUrl + gitWebUrl;
-    }
-
-    GitWebType gitWebType = gitWebConfig.getGitWebType();
-    String revUrl = gitWebType.getRevision();
-
-    ParameterizedString pattern = new ParameterizedString(revUrl);
-    final Map<String, String> p = new HashMap<String, String>();
-    p.put("project", URLEncoder.encode(
-        gitWebType.replacePathSeparator(hook.refUpdate.project), "US-ASCII"));
-    p.put("commit", hook.refUpdate.newRev);
-    return new URL(gitWebUrl + pattern.replace(p));
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/GerritHookFilterChangeState.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/GerritHookFilterChangeState.java
deleted file mode 100644
index 9056357..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/GerritHookFilterChangeState.java
+++ /dev/null
@@ -1,268 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.hooks.workflow;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.gerrit.server.config.SitePath;
-import com.google.gerrit.server.data.ApprovalAttribute;
-import com.google.gerrit.server.data.ChangeAttribute;
-import com.google.gerrit.server.events.ChangeAbandonedEvent;
-import com.google.gerrit.server.events.ChangeMergedEvent;
-import com.google.gerrit.server.events.ChangeRestoredEvent;
-import com.google.gerrit.server.events.CommentAddedEvent;
-import com.google.gerrit.server.events.PatchSetCreatedEvent;
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.hooks.its.InvalidTransitionException;
-import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-import com.googlesource.gerrit.plugins.hooks.util.IssueExtractor;
-
-public class GerritHookFilterChangeState extends GerritHookFilter {
-  private static final Logger log = LoggerFactory
-      .getLogger(GerritHookFilterChangeState.class);
-
-  @Inject
-  private ItsFacade its;
-
-  @Inject
-  @SitePath
-  private File sitePath;
-
-  @Inject
-  private IssueExtractor issueExtractor;
-
-  @Override
-  public void doFilter(PatchSetCreatedEvent hook) throws IOException {
-    performAction(hook.change, new Condition("change", "created"));
-  }
-
-  @Override
-  public void doFilter(CommentAddedEvent hook) throws IOException {
-    try {
-      List<Condition> conditions = new ArrayList<Condition>();
-      conditions.add(new Condition("change", "commented"));
-
-      if (hook.approvals != null) {
-        for (ApprovalAttribute approval : hook.approvals) {
-          addApprovalCategoryCondition(conditions, approval.type, approval.value);
-        }
-      }
-
-      performAction(hook.change,
-          conditions.toArray(new Condition[conditions.size()]));
-    } catch (InvalidTransitionException ex) {
-      log.warn(ex.getMessage());
-    }
-  }
-
-  @Override
-  public void doFilter(ChangeMergedEvent hook) throws IOException {
-    performAction(hook.change, new Condition("change", "merged"));
-  }
-
-  @Override
-  public void doFilter(ChangeAbandonedEvent hook) throws IOException {
-    performAction(hook.change, new Condition("change", "abandoned"));
-  }
-
-  @Override
-  public void doFilter(ChangeRestoredEvent hook) throws IOException {
-    performAction(hook.change, new Condition("change", "restored"));
-  }
-
-  private void addApprovalCategoryCondition(List<Condition> conditions,
-      String name, String value) {
-    value = toConditionValue(value);
-    if (value == null) return;
-
-    conditions.add(new Condition(name, value));
-  }
-
-  private String toConditionValue(String text) {
-    if (text == null) return null;
-
-    try {
-      int val = Integer.parseInt(text);
-      if (val > 0)
-        return "+" + val;
-      else
-        return text;
-    } catch (Exception any) {
-      return null;
-    }
-  }
-
-  private void performAction(ChangeAttribute change, Condition... conditionArgs)
-      throws IOException {
-
-    List<Condition> conditions = Arrays.asList(conditionArgs);
-
-    log.debug("Checking suitable transition for: " + conditions);
-
-    Transition transition = null;
-    List<Transition> transitions = loadTransitions();
-    for (Transition tx : transitions) {
-
-      log.debug("Checking transition: " + tx);
-      if (tx.matches(conditions)) {
-        log.debug("Transition FOUND > " + tx.getAction());
-        transition = tx;
-        break;
-      }
-    }
-
-    if (transition == null) {
-      log.debug("Nothing to perform, transition not found for conditions "
-          + conditions);
-      return;
-    }
-
-    String gitComment = change.subject;
-    String[] issues = issueExtractor.getIssueIds(gitComment);
-
-    for (String issue : issues) {
-      its.performAction(issue, transition.getAction());
-    }
-  }
-
-  private List<Transition> loadTransitions() {
-    File configFile = new File(sitePath, "etc/issue-state-transition.config");
-    FileBasedConfig cfg = new FileBasedConfig(configFile, FS.DETECTED);
-    try {
-      cfg.load();
-    } catch (IOException e) {
-      log.error("Cannot load transitions configuration file " + cfg, e);
-      return Collections.emptyList();
-    } catch (ConfigInvalidException e) {
-      log.error("Invalid transitions configuration file" + cfg, e);
-      return Collections.emptyList();
-    }
-
-    List<Transition> transitions = new ArrayList<Transition>();
-    Set<String> sections = cfg.getSubsections("action");
-    for (String section : sections) {
-      List<Condition> conditions = new ArrayList<Condition>();
-      Set<String> keys = cfg.getNames("action", section);
-      for (String key : keys) {
-        String val = cfg.getString("action", section, key);
-        conditions.add(new Condition(key.trim(), val.trim().split(",")));
-      }
-      transitions.add(new Transition(toAction(section), conditions));
-    }
-    return transitions;
-  }
-
-  private String toAction(String name) {
-    name = name.trim();
-    try {
-      int i = name.lastIndexOf(' ');
-      Integer.parseInt(name.substring(i + 1));
-      name = name.substring(0, i);
-    } catch (Exception ignore) {
-    }
-    return name;
-  }
-
-  public class Condition {
-    private String key;
-    private String[] val;
-
-    public Condition(String key, String[] values) {
-      super();
-      this.key = key.toLowerCase();
-      this.val = values;
-    }
-
-    public Condition(String key, String value) {
-      this(key, new String[] {value});
-    }
-
-    public String getKey() {
-      return key;
-    }
-
-    public String[] getVal() {
-      return val;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      try {
-        Condition other = (Condition) o;
-        if (!(key.equals(other.key))) return false;
-
-        boolean valMatch = false;
-        List<String> otherVals = Arrays.asList(other.val);
-        for (String value : val) {
-          if (otherVals.contains(value)) valMatch = true;
-        }
-
-        return valMatch;
-      } catch (Exception any) {
-        return false;
-      }
-    }
-
-    @Override
-    public String toString() {
-      return key + "=" + Arrays.asList(val);
-    }
-  }
-
-  public class Transition {
-    private String action;
-    private List<Condition> conditions;
-
-    public Transition(String action, List<Condition> conditions) {
-      super();
-      this.action = action;
-      this.conditions = conditions;
-    }
-
-    public String getAction() {
-      return action;
-    }
-
-    public List<Condition> getCondition() {
-      return conditions;
-    }
-
-    public boolean matches(List<Condition> eventConditions) {
-
-      for (Condition condition : conditions) {
-        if (!eventConditions.contains(condition)) return false;
-      }
-
-      return true;
-    }
-
-    @Override
-    public String toString() {
-      return "action=\"" + action + "\", conditions=" + conditions;
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/ItsHookModule.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/ItsHookModule.java
new file mode 100644
index 0000000..e2fff3f
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/ItsHookModule.java
@@ -0,0 +1,69 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.its.base;
+
+import com.google.gerrit.common.EventListener;
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.config.ProjectConfigEntry;
+import com.google.gerrit.server.git.validators.CommitValidationListener;
+
+import com.googlesource.gerrit.plugins.its.base.its.ItsConfig;
+import com.googlesource.gerrit.plugins.its.base.its.ItsHookEnabledConfigEntry;
+import com.googlesource.gerrit.plugins.its.base.validation.ItsValidateComment;
+import com.googlesource.gerrit.plugins.its.base.workflow.ActionController;
+import com.googlesource.gerrit.plugins.its.base.workflow.ActionRequest;
+import com.googlesource.gerrit.plugins.its.base.workflow.Condition;
+import com.googlesource.gerrit.plugins.its.base.workflow.Property;
+import com.googlesource.gerrit.plugins.its.base.workflow.Rule;
+import com.googlesource.gerrit.plugins.its.base.workflow.action.AddComment;
+import com.googlesource.gerrit.plugins.its.base.workflow.action.AddStandardComment;
+import com.googlesource.gerrit.plugins.its.base.workflow.action.AddVelocityComment;
+import com.googlesource.gerrit.plugins.its.base.workflow.action.LogEvent;
+
+public class ItsHookModule extends FactoryModule {
+
+  private final String pluginName;
+  private final PluginConfigFactory pluginCfgFactory;
+
+  public ItsHookModule(@PluginName String pluginName,
+      PluginConfigFactory pluginCfgFactory) {
+    this.pluginName = pluginName;
+    this.pluginCfgFactory = pluginCfgFactory;
+  }
+
+  @Override
+  protected void configure() {
+    bind(ProjectConfigEntry.class)
+        .annotatedWith(Exports.named("enabled"))
+        .toInstance(new ItsHookEnabledConfigEntry(pluginName, pluginCfgFactory));
+    bind(ItsConfig.class);
+    DynamicSet.bind(binder(), CommitValidationListener.class).to(
+        ItsValidateComment.class);
+    DynamicSet.bind(binder(), EventListener.class).to(
+        ActionController.class);
+    factory(ActionRequest.Factory.class);
+    factory(Property.Factory.class);
+    factory(Condition.Factory.class);
+    factory(Rule.Factory.class);
+    factory(AddComment.Factory.class);
+    factory(AddStandardComment.Factory.class);
+    factory(AddVelocityComment.Factory.class);
+    factory(LogEvent.Factory.class);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/its/InitIts.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/InitIts.java
similarity index 97%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/its/InitIts.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/its/InitIts.java
index 4c59b5c..1867a10 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/its/InitIts.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/InitIts.java
@@ -12,15 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.its;
+package com.googlesource.gerrit.plugins.its.base.its;
 
 import com.google.common.base.Strings;
 import com.google.gerrit.common.data.RefConfigSection;
 import com.google.gerrit.pgm.init.api.AllProjectsConfig;
 import com.google.gerrit.pgm.init.api.AllProjectsNameOnInitProvider;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
 import com.google.gerrit.pgm.init.api.InitStep;
 import com.google.gerrit.pgm.init.api.Section;
-import com.google.gerrit.pgm.init.api.ConsoleUI;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Config;
@@ -29,7 +29,7 @@
 
 public class InitIts implements InitStep {
 
-  public static String COMMENT_LINK_SECTION = "commentLink";
+  public static String COMMENT_LINK_SECTION = "commentlink";
 
   public static enum TrueFalseEnum {
     TRUE, FALSE;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/its/InvalidTransitionException.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/InvalidTransitionException.java
similarity index 93%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/its/InvalidTransitionException.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/its/InvalidTransitionException.java
index 38b3178..78c6ec5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/its/InvalidTransitionException.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/InvalidTransitionException.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.its;
+package com.googlesource.gerrit.plugins.its.base.its;
 
 import java.io.IOException;
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfig.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfig.java
new file mode 100644
index 0000000..46cfe92
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfig.java
@@ -0,0 +1,196 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.its.base.its;
+
+import com.google.gerrit.common.data.RefConfigSection;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.events.ChangeAbandonedEvent;
+import com.google.gerrit.server.events.ChangeMergedEvent;
+import com.google.gerrit.server.events.ChangeRestoredEvent;
+import com.google.gerrit.server.events.CommentAddedEvent;
+import com.google.gerrit.server.events.DraftPublishedEvent;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.PatchSetCreatedEvent;
+import com.google.gerrit.server.events.RefUpdatedEvent;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.RefPatternMatcher;
+import com.google.inject.Inject;
+
+import com.googlesource.gerrit.plugins.its.base.validation.ItsAssociationPolicy;
+
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.regex.Pattern;
+
+
+public class ItsConfig {
+  private static final Logger log = LoggerFactory.getLogger(ItsConfig.class);
+
+  private final String pluginName;
+  private final ProjectCache projectCache;
+  private final PluginConfigFactory pluginCfgFactory;
+  private final Config gerritConfig;
+
+  @Inject
+  public ItsConfig(@PluginName String pluginName, ProjectCache projectCache,
+      PluginConfigFactory pluginCfgFactory, @GerritServerConfig Config gerritConfig) {
+    this.pluginName = pluginName;
+    this.projectCache = projectCache;
+    this.pluginCfgFactory = pluginCfgFactory;
+    this.gerritConfig = gerritConfig;
+  }
+
+  // Plugin enablement --------------------------------------------------------
+
+  public boolean isEnabled(Event event) {
+    if (event instanceof PatchSetCreatedEvent) {
+      PatchSetCreatedEvent e = (PatchSetCreatedEvent) event;
+      return isEnabled(e.change.project, e.getRefName());
+    } else if (event instanceof CommentAddedEvent) {
+      CommentAddedEvent e = (CommentAddedEvent) event;
+      return isEnabled(e.change.project, e.getRefName());
+    } else if (event instanceof ChangeMergedEvent) {
+      ChangeMergedEvent e = (ChangeMergedEvent) event;
+      return isEnabled(e.change.project, e.getRefName());
+    } else if (event instanceof ChangeAbandonedEvent) {
+      ChangeAbandonedEvent e = (ChangeAbandonedEvent) event;
+      return isEnabled(e.change.project, e.getRefName());
+    } else if (event instanceof ChangeRestoredEvent) {
+      ChangeRestoredEvent e = (ChangeRestoredEvent) event;
+      return isEnabled(e.change.project, e.getRefName());
+    } else if (event instanceof DraftPublishedEvent) {
+      DraftPublishedEvent e = (DraftPublishedEvent) event;
+      return isEnabled(e.change.project, e.getRefName());
+    } else if (event instanceof RefUpdatedEvent) {
+      RefUpdatedEvent e = (RefUpdatedEvent) event;
+      return isEnabled(e.refUpdate.project, e.refUpdate.refName);
+    } else {
+      log.debug("Event " + event + " not recognised and ignored");
+      return false;
+    }
+  }
+
+  public boolean isEnabled(String project, String refName) {
+    ProjectState projectState = projectCache.get(new Project.NameKey(project));
+    if (projectState == null) {
+      log.error("Failed to check if " + pluginName + " is enabled for project "
+          + project + ": Project " + project + " not found");
+      return false;
+    }
+
+    for (ProjectState parentState : projectState.treeInOrder()) {
+      PluginConfig parentCfg =
+          pluginCfgFactory.getFromProjectConfig(parentState, pluginName);
+      if ("enforced".equals(parentCfg.getString("enabled"))
+          && isEnabledForBranch(parentState, refName)) {
+        return true;
+      }
+    }
+
+    return pluginCfgFactory.getFromProjectConfigWithInheritance(
+        projectState, pluginName).getBoolean("enabled", false)
+        && isEnabledForBranch(projectState, refName);
+  }
+
+  private boolean isEnabledForBranch(ProjectState project, String refName) {
+    String[] refPatterns =
+        pluginCfgFactory.getFromProjectConfigWithInheritance(project,
+            pluginName).getStringList("branch");
+    if (refPatterns.length == 0) {
+      return true;
+    }
+    for (String refPattern : refPatterns) {
+      if (RefConfigSection.isValid(refPattern) && match(refName, refPattern)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean match(String refName, String refPattern) {
+    return RefPatternMatcher.getMatcher(refPattern).match(refName, null);
+  }
+
+  // Issue association --------------------------------------------------------
+
+  /**
+   * Gets the name of the comment link that should be used
+   *
+   * @return name of the comment link that should be used
+   */
+  public String getCommentLinkName() {
+    String ret;
+
+    ret = gerritConfig.getString(pluginName, null, "commentlink");
+    if (ret == null) {
+      ret = pluginName;
+    }
+
+    return ret;
+  }
+
+  /**
+   * Gets the regular expression used to identify issue ids.
+   * <p>
+   * The index of the group that holds the issue id is
+   * {@link #getIssuePatternGroupIndex()}.
+   *
+   * @return the regular expression, or {@code null}, if there is no pattern
+   *    to match issue ids.
+   */
+  public Pattern getIssuePattern() {
+    Pattern ret = null;
+    String match = gerritConfig.getString("commentlink",
+        getCommentLinkName(), "match");
+    if (match != null) {
+      ret = Pattern.compile(match);
+    }
+    return ret;
+  }
+
+  /**
+   * Gets the index of the group in the issue pattern that holds the issue id.
+   * <p>
+   * The corresponding issue pattern is {@link #getIssuePattern()}
+   *
+   * @return the group index for {@link #getIssuePattern()} that holds the
+   *     issue id. The group index is guaranteed to be a valid group index.
+   */
+  public int getIssuePatternGroupIndex() {
+    Pattern pattern = getIssuePattern();
+    int groupCount = pattern.matcher("").groupCount();
+    int index = gerritConfig.getInt(pluginName, "commentlinkGroupIndex", 1);
+    if (index < 0 || index > groupCount) {
+      index = (groupCount == 0 ? 0 : 1);
+    }
+    return index;
+  }
+
+  /**
+   * Gets how necessary it is to associate commits with issues
+   * @return policy on how necessary association with issues is
+   */
+  public ItsAssociationPolicy getItsAssociationPolicy() {
+    return gerritConfig.getEnum("commentlink", getCommentLinkName(),
+        "association", ItsAssociationPolicy.OPTIONAL);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsFacade.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsFacade.java
similarity index 95%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsFacade.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsFacade.java
index b4f9cd6..53e3ced 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsFacade.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsFacade.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.its;
+package com.googlesource.gerrit.plugins.its.base.its;
 
 import java.io.IOException;
 import java.net.URL;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsHookEnabledConfigEntry.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsHookEnabledConfigEntry.java
similarity index 96%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsHookEnabledConfigEntry.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsHookEnabledConfigEntry.java
index 03b7d52..17bcf6b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsHookEnabledConfigEntry.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsHookEnabledConfigEntry.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.its;
+package com.googlesource.gerrit.plugins.its.base.its;
 
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/its/NoopItsFacade.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/NoopItsFacade.java
similarity index 97%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/its/NoopItsFacade.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/its/NoopItsFacade.java
index b7bfe44..1d563e7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/its/NoopItsFacade.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/NoopItsFacade.java
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.its;
-
-import java.io.IOException;
-import java.net.URL;
+package com.googlesource.gerrit.plugins.its.base.its;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
+import java.net.URL;
+
 /**
  * An ITS facade doing nothing, it's configured when no ITS are referenced in
  * config
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/util/CommitMessageFetcher.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/util/CommitMessageFetcher.java
similarity index 82%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/util/CommitMessageFetcher.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/util/CommitMessageFetcher.java
index 83b33ce..76a1a5a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/util/CommitMessageFetcher.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/util/CommitMessageFetcher.java
@@ -1,6 +1,4 @@
-package com.googlesource.gerrit.plugins.hooks.util;
-
-import java.io.IOException;
+package com.googlesource.gerrit.plugins.its.base.util;
 
 import com.google.gerrit.reviewdb.client.Project.NameKey;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -13,6 +11,8 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
+
 public class CommitMessageFetcher {
   private static final Logger log = LoggerFactory.getLogger(
       CommitMessageFetcher.class);
@@ -25,11 +25,11 @@
   }
 
   public String fetch(String projectName, String commitId) throws IOException {
-    try (Repository repo = repoManager.openRepository(new NameKey(projectName));
-        RevWalk revWalk = new RevWalk(repo)){
-      RevCommit commit = revWalk.parseCommit(ObjectId.fromString(commitId));
-
-      return commit.getFullMessage();
+    try (Repository repo = repoManager.openRepository(new NameKey(projectName))) {
+      try (RevWalk revWalk = new RevWalk(repo)) {
+        RevCommit commit = revWalk.parseCommit(ObjectId.fromString(commitId));
+        return commit.getFullMessage();
+      }
     }
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/util/IssueExtractor.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/util/IssueExtractor.java
similarity index 88%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/util/IssueExtractor.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/util/IssueExtractor.java
index cdf4a09..546e6c7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/util/IssueExtractor.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/util/IssueExtractor.java
@@ -1,42 +1,39 @@
-package com.googlesource.gerrit.plugins.hooks.util;
+package com.googlesource.gerrit.plugins.its.base.util;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import com.googlesource.gerrit.plugins.its.base.its.ItsConfig;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.Map;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-
-import org.apache.commons.lang.StringUtils;
-import org.eclipse.jgit.lib.Config;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 public class IssueExtractor {
   private static final Logger log = LoggerFactory.getLogger(
       IssueExtractor.class);
 
-  private final Config gerritConfig;
-  private final String pluginName;
   private final CommitMessageFetcher commitMessageFetcher;
   private final ReviewDb db;
+  private final ItsConfig itsConfig;
 
   @Inject
-  IssueExtractor(@GerritServerConfig Config gerritConfig,
-      @PluginName String pluginName, CommitMessageFetcher commitMessageFetcher,
+  IssueExtractor(ItsConfig itsConfig,
+      CommitMessageFetcher commitMessageFetcher,
       ReviewDb db) {
-    this.gerritConfig = gerritConfig;
-    this.pluginName = pluginName;
     this.commitMessageFetcher = commitMessageFetcher;
     this.db = db;
+    this.itsConfig = itsConfig;
   }
 
   /**
@@ -46,7 +43,7 @@
    * @return array of {@link String}. Each String being a found issue id.
    */
   public String[] getIssueIds(String haystack) {
-    Pattern pattern = getPattern();
+    Pattern pattern = itsConfig.getIssuePattern();
     if (pattern == null) return new String[] {};
 
     log.debug("Matching '" + haystack + "' against " + pattern.pattern());
@@ -54,29 +51,18 @@
     Set<String> issues = Sets.newHashSet();
     Matcher matcher = pattern.matcher(haystack);
 
+    int groupIdx = itsConfig.getIssuePatternGroupIndex();
     while (matcher.find()) {
-      int groupIdx = Math.min(matcher.groupCount(), 1);
-      issues.add(matcher.group(groupIdx));
+      String issueId = matcher.group(groupIdx);
+      if (!Strings.isNullOrEmpty(issueId)) {
+        issues.add(issueId);
+      }
     }
 
     return issues.toArray(new String[issues.size()]);
   }
 
   /**
-   * Gets the regular expression used to identify issue ids.
-   * @return the regular expression, or {@code null}, if there is no pattern
-   *    to match issue ids.
-   */
-  public Pattern getPattern() {
-    Pattern ret = null;
-    String match = gerritConfig.getString("commentLink", pluginName, "match");
-    if (match != null) {
-      ret = Pattern.compile(match);
-    }
-    return ret;
-  }
-
-  /**
    * Helper funcion for {@link #getIssueIds(String, String)}.
    * <p>
    * Adds a text's issues for a given occurrence to the map returned by
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/util/PropertyAttributeExtractor.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyAttributeExtractor.java
similarity index 85%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/util/PropertyAttributeExtractor.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyAttributeExtractor.java
index e6dfe7a..f928f02 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/util/PropertyAttributeExtractor.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyAttributeExtractor.java
@@ -1,20 +1,18 @@
-//Copyright (C) 2013 The Android Open Source Project
+// 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
+// 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
+// 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.
+// 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.util;
-
-import java.util.Set;
+package com.googlesource.gerrit.plugins.its.base.util;
 
 import com.google.common.collect.Sets;
 import com.google.gerrit.server.data.AccountAttribute;
@@ -24,7 +22,9 @@
 import com.google.gerrit.server.data.RefUpdateAttribute;
 import com.google.inject.Inject;
 
-import com.googlesource.gerrit.plugins.hooks.workflow.Property;
+import com.googlesource.gerrit.plugins.its.base.workflow.Property;
+
+import java.util.Set;
 
 /**
  * Extractor to translate the various {@code *Attribute}s to
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/util/PropertyExtractor.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractor.java
similarity index 89%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/util/PropertyExtractor.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractor.java
index 0f6f937..55d5a8d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/util/PropertyExtractor.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractor.java
@@ -1,23 +1,21 @@
-//Copyright (C) 2013 The Android Open Source Project
+// 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
+// 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
+// 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.
+// 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.util;
-
-import java.util.Map;
-import java.util.Set;
+package com.googlesource.gerrit.plugins.its.base.util;
 
 import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.server.data.ApprovalAttribute;
@@ -32,7 +30,10 @@
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.inject.Inject;
 
-import com.googlesource.gerrit.plugins.hooks.workflow.Property;
+import com.googlesource.gerrit.plugins.its.base.workflow.Property;
+
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Extractor to translate an {@link ChangeEvent} to
@@ -42,14 +43,17 @@
   private IssueExtractor issueExtractor;
   private Property.Factory propertyFactory;
   private PropertyAttributeExtractor propertyAttributeExtractor;
+  private final String pluginName;
 
   @Inject
   PropertyExtractor(IssueExtractor issueExtractor,
       Property.Factory propertyFactory,
-      PropertyAttributeExtractor propertyAttributeExtractor) {
+      PropertyAttributeExtractor propertyAttributeExtractor,
+      @PluginName String pluginName) {
     this.issueExtractor = issueExtractor;
     this.propertyFactory = propertyFactory;
     this.propertyAttributeExtractor = propertyAttributeExtractor;
+    this.pluginName = pluginName;
   }
 
   /**
@@ -226,6 +230,8 @@
         Set<Property> properties = Sets.newHashSet();
         Property property = propertyFactory.create("issue", issue);
         properties.add(property);
+        property = propertyFactory.create("its-name", pluginName);
+        properties.add(property);
         for (String occurrence: associations.get(issue)) {
           property = propertyFactory.create("association", occurrence);
           properties.add(property);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/validation/ItsAssociationPolicy.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/validation/ItsAssociationPolicy.java
similarity index 91%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/validation/ItsAssociationPolicy.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/validation/ItsAssociationPolicy.java
index 30788c2..ea363a8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/validation/ItsAssociationPolicy.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/validation/ItsAssociationPolicy.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.validation;
+package com.googlesource.gerrit.plugins.its.base.validation;
 
 public enum ItsAssociationPolicy {
     MANDATORY, SUGGESTED, OPTIONAL;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateComment.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateComment.java
similarity index 82%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateComment.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateComment.java
index bd8ed2c..c05118a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateComment.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateComment.java
@@ -12,30 +12,27 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.validation;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.transport.ReceiveCommand;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+package com.googlesource.gerrit.plugins.its.base.validation;
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.events.CommitReceivedEvent;
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidationListener;
 import com.google.gerrit.server.git.validators.CommitValidationMessage;
 import com.google.inject.Inject;
 
-import com.googlesource.gerrit.plugins.hooks.its.ItsConfig;
-import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-import com.googlesource.gerrit.plugins.hooks.util.IssueExtractor;
+import com.googlesource.gerrit.plugins.its.base.its.ItsConfig;
+import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
+import com.googlesource.gerrit.plugins.its.base.util.IssueExtractor;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
 
 public class ItsValidateComment implements CommitValidationListener {
 
@@ -45,10 +42,6 @@
   @Inject
   private ItsFacade client;
 
-  @Inject
-  @GerritServerConfig
-  private Config gerritConfig;
-
   @Inject @PluginName
   private String pluginName;
 
@@ -58,9 +51,10 @@
   @Inject
   private IssueExtractor issueExtractor;
 
-  private List<CommitValidationMessage> validCommit(ReceiveCommand cmd, RevCommit commit) throws CommitValidationException {
+  private List<CommitValidationMessage> validCommit(RevCommit commit)
+      throws CommitValidationException {
     List<CommitValidationMessage> ret = Lists.newArrayList();
-    ItsAssociationPolicy associationPolicy = getItsAssociationPolicy();
+    ItsAssociationPolicy associationPolicy = itsConfig.getItsAssociationPolicy();
 
     switch (associationPolicy) {
       case MANDATORY:
@@ -118,7 +112,7 @@
           sb.append("Hint: insert one or more issue-id anywhere in the ");
           sb.append("commit message.\n");
           sb.append("      Issue-ids are strings matching ");
-          sb.append(issueExtractor.getPattern().pattern());
+          sb.append(itsConfig.getIssuePattern().pattern());
           sb.append("\n");
           sb.append("      and are pointing to existing tickets on ");
           sb.append(pluginName);
@@ -135,16 +129,11 @@
     return ret;
   }
 
-  private ItsAssociationPolicy getItsAssociationPolicy() {
-    return gerritConfig.getEnum("commentLink", pluginName, "association",
-        ItsAssociationPolicy.OPTIONAL);
-  }
-
   private CommitValidationMessage commitValidationFailure(
       String synopsis, String details) throws CommitValidationException {
     CommitValidationMessage ret =
         new CommitValidationMessage(synopsis + "\n" + details, false);
-    if (getItsAssociationPolicy() == ItsAssociationPolicy.MANDATORY) {
+    if (itsConfig.getItsAssociationPolicy() == ItsAssociationPolicy.MANDATORY) {
       throw new CommitValidationException(synopsis,
           Collections.singletonList(ret));
     }
@@ -155,7 +144,7 @@
   public List<CommitValidationMessage> onCommitReceived(
       CommitReceivedEvent receiveEvent) throws CommitValidationException {
     if (itsConfig.isEnabled(receiveEvent.project.getName(), receiveEvent.refName)) {
-      return validCommit(receiveEvent.command, receiveEvent.commit);
+      return validCommit(receiveEvent.commit);
     } else {
       return Collections.emptyList();
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionController.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionController.java
similarity index 91%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionController.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionController.java
index 885db3e..9e7bb7d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionController.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionController.java
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.workflow;
+package com.googlesource.gerrit.plugins.its.base.workflow;
 
 import com.google.gerrit.common.EventListener;
 import com.google.gerrit.server.events.Event;
 import com.google.inject.Inject;
 
-import com.googlesource.gerrit.plugins.hooks.its.ItsConfig;
-import com.googlesource.gerrit.plugins.hooks.util.PropertyExtractor;
+import com.googlesource.gerrit.plugins.its.base.its.ItsConfig;
+import com.googlesource.gerrit.plugins.its.base.util.PropertyExtractor;
 
 import java.util.Collection;
 import java.util.Set;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionExecutor.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutor.java
similarity index 66%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionExecutor.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutor.java
index 99ccf85..e83feb4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionExecutor.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutor.java
@@ -1,32 +1,33 @@
-//Copyright (C) 2013 The Android Open Source Project
+// 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
+// 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
+// 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.
+// 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.workflow;
+package com.googlesource.gerrit.plugins.its.base.workflow;
 
-import java.io.IOException;
-import java.util.Set;
+import com.google.inject.Inject;
+
+import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
+import com.googlesource.gerrit.plugins.its.base.workflow.action.Action;
+import com.googlesource.gerrit.plugins.its.base.workflow.action.AddComment;
+import com.googlesource.gerrit.plugins.its.base.workflow.action.AddStandardComment;
+import com.googlesource.gerrit.plugins.its.base.workflow.action.AddVelocityComment;
+import com.googlesource.gerrit.plugins.its.base.workflow.action.LogEvent;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-import com.googlesource.gerrit.plugins.hooks.workflow.action.Action;
-import com.googlesource.gerrit.plugins.hooks.workflow.action.AddComment;
-import com.googlesource.gerrit.plugins.hooks.workflow.action.AddStandardComment;
-import com.googlesource.gerrit.plugins.hooks.workflow.action.AddVelocityComment;
-import com.googlesource.gerrit.plugins.hooks.workflow.action.LogEvent;
+import java.io.IOException;
+import java.util.Set;
 
 /**
  * Executes an {@link ActionRequest}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionRequest.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionRequest.java
similarity index 97%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionRequest.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionRequest.java
index bd81cb1..0394e09 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionRequest.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionRequest.java
@@ -12,13 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.workflow;
+package com.googlesource.gerrit.plugins.its.base.workflow;
 
-import java.util.Arrays;
-
+import com.google.gerrit.common.Nullable;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
-import com.google.gerrit.common.Nullable;
+
+import java.util.Arrays;
 
 /**
  * An action to take for an {@code ChangeEvent}.
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Condition.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Condition.java
similarity index 96%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Condition.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Condition.java
index aca340e..13f6cb4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Condition.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Condition.java
@@ -12,11 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.workflow;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
+package com.googlesource.gerrit.plugins.its.base.workflow;
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
@@ -24,7 +20,11 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
-import com.googlesource.gerrit.plugins.hooks.workflow.action.Action;
+import com.googlesource.gerrit.plugins.its.base.workflow.action.Action;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
 
 /**
  * A condition as used in {@link Rule}, as precondition to {@link Action}s.
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Property.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Property.java
similarity index 96%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Property.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Property.java
index 8b582d6..98a8117 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Property.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Property.java
@@ -12,11 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.workflow;
+package com.googlesource.gerrit.plugins.its.base.workflow;
 
+import com.google.gerrit.common.Nullable;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
-import com.google.gerrit.common.Nullable;
 
 /**
  * A property to match against {@code Condition}s.
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Rule.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Rule.java
similarity index 97%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Rule.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Rule.java
index 5fb58d1..4ec6be3 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Rule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Rule.java
@@ -12,18 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.workflow;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
+package com.googlesource.gerrit.plugins.its.base.workflow;
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
 /**
  * A single rule that associates {@code Action}s to {@code Condition}s.
  */
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBase.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleBase.java
similarity index 92%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBase.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleBase.java
index cfa70ac..2f0e1a2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleBase.java
@@ -12,11 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.workflow;
+package com.googlesource.gerrit.plugins.its.base.workflow;
 
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.annotations.PluginName;
@@ -29,6 +26,11 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+
 /**
  * Collection and matcher agains {@link Rule}s.
  */
@@ -56,7 +58,7 @@
    */
   private static final String ACTION_KEY = "action";
 
-  private final File sitePath;
+  private final Path sitePath;
   private final Rule.Factory ruleFactory;
   private final Condition.Factory conditionFactory;
   private final ActionRequest.Factory actionRequestFactory;
@@ -69,7 +71,7 @@
   }
 
   @Inject
-  public RuleBase(@SitePath File sitePath, Rule.Factory ruleFactory,
+  public RuleBase(@SitePath Path sitePath, Rule.Factory ruleFactory,
       Condition.Factory conditionFactory,
       ActionRequest.Factory actionRequestFactory,
       @PluginName String pluginName) {
@@ -135,7 +137,7 @@
     // "actions.config" (with trailing "s", we (for now) load files from both
     // locations, but consider "actions.config" (with trailing "s" the
     // canonical place.
-    File faultyNameRuleFile = new File(sitePath, "etc" + File.separatorChar
+    File faultyNameRuleFile = new File(sitePath.toFile(), "etc" + File.separatorChar
         + "its" + File.separator + "action.config");
     if (faultyNameRuleFile.exists()) {
       log.warn("Loading rules from deprecated 'etc/its/action.config' (No "
@@ -145,12 +147,12 @@
     }
 
     // Add global rules
-    File globalRuleFile = new File(sitePath, ITS_CONFIG_FILE_START +
+    File globalRuleFile = new File(sitePath.toFile(), ITS_CONFIG_FILE_START +
         ITS_CONFIG_FILE_END);
     addRulesFromFile(globalRuleFile);
 
     // Add its-specific rules
-    File itsSpecificRuleFile = new File(sitePath, ITS_CONFIG_FILE_START + "-" +
+    File itsSpecificRuleFile = new File(sitePath.toFile(), ITS_CONFIG_FILE_START + "-" +
         pluginName + ITS_CONFIG_FILE_END);
     addRulesFromFile(itsSpecificRuleFile);
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/Action.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/action/Action.java
similarity index 83%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/Action.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/action/Action.java
index 7e0595c..24feee5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/Action.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/action/Action.java
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.workflow.action;
+package com.googlesource.gerrit.plugins.its.base.workflow.action;
+
+import com.googlesource.gerrit.plugins.its.base.workflow.ActionRequest;
+import com.googlesource.gerrit.plugins.its.base.workflow.Property;
 
 import java.io.IOException;
 import java.util.Set;
 
-import com.googlesource.gerrit.plugins.hooks.workflow.ActionRequest;
-import com.googlesource.gerrit.plugins.hooks.workflow.Property;
-
 /**
  * Interface for actions on an issue tracking system
  */
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddComment.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddComment.java
similarity index 84%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddComment.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddComment.java
index b83d205..8ca9c59 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddComment.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddComment.java
@@ -12,18 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.workflow.action;
-
-import java.io.IOException;
-import java.util.Set;
-
-import org.apache.commons.lang.StringUtils;
+package com.googlesource.gerrit.plugins.its.base.workflow.action;
 
 import com.google.common.base.Strings;
 import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-import com.googlesource.gerrit.plugins.hooks.workflow.ActionRequest;
-import com.googlesource.gerrit.plugins.hooks.workflow.Property;
+
+import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
+import com.googlesource.gerrit.plugins.its.base.workflow.ActionRequest;
+import com.googlesource.gerrit.plugins.its.base.workflow.Property;
+
+import org.apache.commons.lang.StringUtils;
+
+import java.io.IOException;
+import java.util.Set;
 
 /**
  * Adds a fixed comment to an issue.
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddStandardComment.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddStandardComment.java
similarity index 93%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddStandardComment.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddStandardComment.java
index 8cdeef7..814075d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddStandardComment.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddStandardComment.java
@@ -12,18 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.workflow.action;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.Set;
+package com.googlesource.gerrit.plugins.its.base.workflow.action;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.Maps;
 import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-import com.googlesource.gerrit.plugins.hooks.workflow.ActionRequest;
-import com.googlesource.gerrit.plugins.hooks.workflow.Property;
+
+import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
+import com.googlesource.gerrit.plugins.its.base.workflow.ActionRequest;
+import com.googlesource.gerrit.plugins.its.base.workflow.Property;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Adds a short predefined comments to an issue.
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddVelocityComment.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddVelocityComment.java
similarity index 81%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddVelocityComment.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddVelocityComment.java
index e2db316..f3a1cc2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddVelocityComment.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddVelocityComment.java
@@ -12,27 +12,29 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.workflow.action;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.util.Arrays;
-import java.util.Set;
-
-import org.apache.commons.lang.StringUtils;
-import org.apache.velocity.VelocityContext;
-import org.apache.velocity.runtime.RuntimeInstance;
-import org.parboiled.common.FileUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+package com.googlesource.gerrit.plugins.its.base.workflow.action;
 
 import com.google.common.base.Strings;
 import com.google.gerrit.server.config.SitePath;
 import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-import com.googlesource.gerrit.plugins.hooks.workflow.ActionRequest;
-import com.googlesource.gerrit.plugins.hooks.workflow.Property;
+
+import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
+import com.googlesource.gerrit.plugins.its.base.workflow.ActionRequest;
+import com.googlesource.gerrit.plugins.its.base.workflow.Property;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.runtime.RuntimeInstance;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Set;
 
 /**
  * Adds a short predefined comments to an issue.
@@ -55,11 +57,11 @@
       "its" + File.separator + "templates";
 
   private final ItsFacade its;
-  private final File sitePath;
+  private final Path sitePath;
   private final RuntimeInstance velocityRuntime;
 
   @Inject
-  public AddVelocityComment(RuntimeInstance velocityRuntime, @SitePath File sitePath, ItsFacade its) {
+  public AddVelocityComment(RuntimeInstance velocityRuntime, @SitePath Path sitePath, ItsFacade its) {
     this.velocityRuntime = velocityRuntime;
     this.sitePath = sitePath;
     this.its = its;
@@ -82,7 +84,7 @@
     return velocityContext;
   }
 
-  private String velocify(String template, Set<Property> properties) throws IOException {
+  private String velocify(String template, Set<Property> properties) {
     VelocityContext context = getVelocityContext(properties);
     StringWriter w = new StringWriter();
     velocityRuntime.evaluate(context, w, "ItsComment", template);
@@ -103,12 +105,12 @@
       if (templateName.isEmpty()) {
         log.error("No template name given in " + actionRequest);
       } else {
-        File templateFile = new File(sitePath, ITS_TEMPLATE_DIR +
-            File.separator + templateName + ".vm");
-        if (templateFile.canRead()) {
-          template = FileUtils.readAllText(templateFile);
+        Path templateDir = sitePath.resolve(ITS_TEMPLATE_DIR);
+        Path templatePath = templateDir.resolve(templateName + ".vm");
+        if (Files.isReadable(templatePath)) {
+          template = new String(Files.readAllBytes(templatePath));
         } else {
-          log.error("Cannot read template " + templateFile);
+          log.error("Cannot read template " + templatePath);
         }
       }
     }
@@ -125,10 +127,10 @@
   // it up, if it is public.
   public class VelocityAdapterItsFacade {
 
-    private final ItsFacade its;
+    private final ItsFacade facade;
 
-    private VelocityAdapterItsFacade(ItsFacade its) {
-      this.its = its;
+    private VelocityAdapterItsFacade(ItsFacade facade) {
+      this.facade = facade;
     }
 
     /**
@@ -139,7 +141,7 @@
      * @return Link to the given URL in the used Its' syntax.
      */
     public String formatLink(String url, String caption) {
-      return its.createLinkForWebui(url, caption);
+      return facade.createLinkForWebui(url, caption);
     }
 
     /**
@@ -151,7 +153,7 @@
      * @return Link to the given URL in the used Its' syntax.
      */
     public String formatLink(String url) {
-      return its.createLinkForWebui(url, url);
+      return facade.createLinkForWebui(url, url);
     }
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/LogEvent.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/action/LogEvent.java
similarity index 89%
rename from src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/LogEvent.java
rename to src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/action/LogEvent.java
index 17b891d..d66dbce 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/LogEvent.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/action/LogEvent.java
@@ -12,17 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.workflow.action;
+package com.googlesource.gerrit.plugins.its.base.workflow.action;
 
-import java.io.IOException;
-import java.util.Set;
+import com.google.inject.Inject;
+
+import com.googlesource.gerrit.plugins.its.base.workflow.ActionRequest;
+import com.googlesource.gerrit.plugins.its.base.workflow.Property;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.hooks.workflow.ActionRequest;
-import com.googlesource.gerrit.plugins.hooks.workflow.Property;
+import java.io.IOException;
+import java.util.Set;
 
 /**
  * Dumps the event's properties to the log.
@@ -33,7 +34,7 @@
 public class LogEvent implements Action {
   private static final Logger log = LoggerFactory.getLogger(LogEvent.class);
 
-  private enum Level { ERROR, WARN, INFO, DEBUG };
+  private enum Level { ERROR, WARN, INFO, DEBUG }
 
   public interface Factory {
     LogEvent create();
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md
index c6afec9..3606c87 100644
--- a/src/main/resources/Documentation/build.md
+++ b/src/main/resources/Documentation/build.md
@@ -29,19 +29,6 @@
   buck test --all --include its-base
 ```
 
-Note that for compatibility reasons a Maven build is provided, but is
-considered to be deprecated and will be removed in a future version of
-this plugin.
-
-To build with Maven, change directory to the plugin folder and issue the
-command:
-
-```
-  mvn clean package
-```
-
-When building with Maven, the Gerrit Plugin API must be available.
-
 Note that the ITS-based plugins require `its-base__plugin` library:
 
 ```
diff --git a/src/main/resources/Documentation/config-common.md b/src/main/resources/Documentation/config-common.md
index 4ad3c97..9e2c033 100644
--- a/src/main/resources/Documentation/config-common.md
+++ b/src/main/resources/Documentation/config-common.md
@@ -5,7 +5,7 @@
 * [Identifying ITS ids][identifying-its-ids]
 * [Enabling ITS integration][enabling-its-integration]
 * [Configuring rules of when to take which actions in the ITS][configure-rules]
-* [Legacy configuration][legacy-configuration]
+* [Further common configuration details][config-common-detail]
 
 
 
@@ -14,15 +14,16 @@
 -----------------------------------------------------
 
 In order to extract ITS ids from commit messages, @PLUGIN@ uses
-[commentlink][upstream-comment-link-doc]s of name "`@PLUGIN@`".
+[commentlink][upstream-comment-link-doc]s of
+([per default][common-config-commentlink]) name "`@PLUGIN@`".
 
-The first group of `commentlink.@PLUGIN@.match` is considered the
-issue id.
+The ([per default][common-config-commentlinkGroupIndex]) first group of
+`commentlink.@PLUGIN@.match` is considered the issue id.
 
 So for example having
 
 ```
-[commentLink "@PLUGIN@"]
+[commentlink "@PLUGIN@"]
     match = [Bb][Uu][Gg][ ]*([1-9][0-9]*)
     html = "<a href=\"http://my.issure.tracker.example.org/show_bug.cgi?id=$1\">(bug $1)</a>"
     association = SUGGESTED
@@ -51,6 +52,8 @@
 :	 Bug-ids are liked when found in the git commit message, no warning is
 	 displayed otherwise.
 
+[upstream-comment-link-doc]: ../../../Documentation/config-gerrit.html#commentlink
+
 
 
 [enabling-its-integration]: #enabling-its-integration
@@ -90,7 +93,7 @@
 
 The issue tracker system integration can be limited to specific
 branches by setting `plugin.@PLUGIN@.branch`. The branches may be
-configured using explicit branch names, ref patterns, or regular
+configured using explicit ref names, ref patterns, or regular
 expressions. Multiple branches may be specified.
 
 E.g. to limit the issue tracker system integration to the `master`
@@ -142,66 +145,29 @@
 
 
 
+[config-common-detail]: #config-common-detail
+<a name="config-common-detail">Further common configuration details</a>
+-----------------------------------------------------------------------
 
-[legacy-configuration]: #legacy-configuration
-<a name="legacy-configuration">Legacy configuration</a>
--------------------------------------------------------
+[common-config-commentlink]: #common-config-commentlink
+[common-config-commentlinkGroupIndex]: #common-config-commentlinkGroupIndex
 
-In this section we present the legacy configuration that uses
-`etc/gerrit.config` directly. As this legacy part will be removed at
-some point, please upgrade to the rule [rule based
-approach][rule-base].
+<a name="common-config-commentlink">`@PLUGIN@.commentlink`
+:   The name of the comment link to use to extract issue ids.
 
-The following configuration settings are available:
+    This setting is useful to reuse the same comment link from different Its
+    plugins. For example, if you set `@PLUGIN@.commentlink` to `foo`, then the
+    comment link `foo` is used (instead of the comment link `@PLUGIN@`) to
+    extract issue ids.
 
-`@PLUGIN@.commentOnChangeAbandoned`
-:	If true, abandoning a change adds an ITS comment to the change's
-	associated issue.
+    Default is `@PLUGIN@`
 
-	Default is `true`.
+<a name="common-config-commentlinkGroupIndex">`@PLUGIN@.commentlinkGroupIndex`
+:   The group index within `@PLUGIN@.commentlink` that holds the issue id.
 
-`@PLUGIN@.commentOnChangeCreated`
-:	If true, creating a change adds an ITS comment to the change's
-	associated issue.
-
-	Default is `false`.
-
-`@PLUGIN@.commentOnChangeMerged`
-:	If true, merging a change's patch set adds an ITS comment to
-	the change's associated issue.
-
-	Default is `true`.
-
-`@PLUGIN@.commentOnChangeRestored`
-:	If true, restoring an abandoned change adds an ITS comment to
-	the change's associated issue.
-
-	Default is `true`.
-
-`@PLUGIN@.commentOnCommentAdded`
-:	If true, adding a comment and/or review to a change in Gerrit
-	adds an ITS comment to the change's associated issue.
-
-	Default is `true`.
-
-`@PLUGIN@.commentOnFirstLinkedPatchSetCreated`
-:	If true, creating a patch set for a change adds an ITS comment
-	to the change's associated issue, if the issue has not been
-	mentioned in previous patch sets of the same change.
-
-	Default is `false`.
-
-`@PLUGIN@.commentOnPatchSetCreated`
-:	If true, creating a patch set for a change adds an ITS comment
-	to the change's associated issue.
-
-	Default is `true`.
-
-`@PLUGIN@.commentOnRefUpdatedGitWeb`
-:	If true, updating a ref adds a GitWeb link to the associated
-	issue.
-
-	Default is `true`.
+    Default is `1`, if there are are groups within the regular expression for
+    the `@PLUGIN@.commentlink` comment link, and the default is `0`, if there
+    are no such groups.
 
 [Back to @PLUGIN@ documentation index][index]
 
diff --git a/src/main/resources/Documentation/config-rulebase-common.md b/src/main/resources/Documentation/config-rulebase-common.md
index 60e1185..ed2946c 100644
--- a/src/main/resources/Documentation/config-rulebase-common.md
+++ b/src/main/resources/Documentation/config-rulebase-common.md
@@ -130,6 +130,28 @@
 : How the issue of property `issue` got associated to this event.
   See [Property: `association`][property-association].
 
+`its-name`
+:   Name of this plugin (i.e.: `@PLUGIN@`). This property can be used to
+    make a rule in the rulebase match only for certain ITS plugins, if more
+    than one is installed.
+
+    For example
+
+    ```
+    [rule "someRuleForBugzillaOnly"]
+      its-name = its-bugzilla
+      approval-Code-Review = -2
+      action = add-comment Heya Bugzilla users, the change had a -2 Code-Review approval.
+    [rule "someRuleForJiraOnly"]
+      its-name = its-jira
+      approval-Code-Review = -2
+      action = add-comment Dear JIRA users, the change had a -2 Code-Review approval.
+    ```
+
+    would report the “Heya Bugzilla...” text only through its-bugzilla for
+    changes that had a -2 Code-Review and have an association through
+    its-bugzilla. And for changes that had a -2 Code-Review and have an
+    association through its-jira, its-jira would report “Dear Jira users, ...”.
 
 The further properties are listed in the event's
 corresponding subsection below:
@@ -482,7 +504,7 @@
 
 [Further actions][further-actions] may be provided by @PLUGIN@.
 
-[further-actions]: config-rulebase-plugin-actions.md
+[further-actions]: config-rulebase-plugin-actions.html
 
 [action-add-comment]: #action-add-comment
 ### <a name="action-add-comment">Action: add-comment</a>
@@ -512,9 +534,6 @@
 (abandoner, merger, ...), the change's subject, a reason (if one has
 been given), and a link to the change.
 
-[action-add-comment]: #action-add-comment
-### <a name="action-add-comment">Action: add-comment</a>
-
 [action-add-velocity-comment]: #action-add-velocity-comment
 ### <a name="action-add-velocity-comment">Action: add-velocity-comment</a>
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfigTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfigTest.java
new file mode 100644
index 0000000..32c3f20
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfigTest.java
@@ -0,0 +1,802 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.its.base.its;
+
+import static org.easymock.EasyMock.expect;
+
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.data.ChangeAttribute;
+import com.google.gerrit.server.data.RefUpdateAttribute;
+import com.google.gerrit.server.events.ChangeAbandonedEvent;
+import com.google.gerrit.server.events.ChangeMergedEvent;
+import com.google.gerrit.server.events.ChangeRestoredEvent;
+import com.google.gerrit.server.events.CommentAddedEvent;
+import com.google.gerrit.server.events.DraftPublishedEvent;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.PatchSetCreatedEvent;
+import com.google.gerrit.server.events.RefUpdatedEvent;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+import com.googlesource.gerrit.plugins.its.base.validation.ItsAssociationPolicy;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.util.Arrays;
+
+public class ItsConfigTest extends LoggingMockingTestCase {
+  private Injector injector;
+
+  private ProjectCache projectCache;
+  private PluginConfigFactory pluginConfigFactory;
+  private Config serverConfig;
+
+  public void setupIsEnabled(String enabled, String parentEnabled,
+      String[] branches) {
+    ProjectState projectState = createMock(ProjectState.class);
+
+    expect(projectCache.get(new Project.NameKey("testProject")))
+        .andReturn(projectState).anyTimes();
+    expect(projectCache.get(new Project.NameKey("parentProject")))
+        .andReturn(projectState).anyTimes();
+
+    Iterable<ProjectState> parents;
+    if (parentEnabled == null) {
+      parents = Arrays.asList(projectState);
+    } else {
+      ProjectState parentProjectState = createMock(ProjectState.class);
+
+      PluginConfig parentPluginConfig = createMock(PluginConfig.class);
+
+      expect(pluginConfigFactory.getFromProjectConfig(
+          parentProjectState, "ItsTestName")).andReturn(parentPluginConfig);
+
+      expect(parentPluginConfig.getString("enabled")).andReturn(parentEnabled)
+          .anyTimes();
+
+      PluginConfig parentPluginConfigWI = createMock(PluginConfig.class);
+
+      expect(pluginConfigFactory.getFromProjectConfigWithInheritance(
+          parentProjectState, "ItsTestName")).andReturn(parentPluginConfigWI)
+          .anyTimes();
+
+      String[] parentBranches = { "refs/heads/testBranch" };
+      expect(parentPluginConfigWI.getStringList("branch"))
+          .andReturn(parentBranches).anyTimes();
+
+      parents = Arrays.asList(parentProjectState, projectState);
+    }
+    expect(projectState.treeInOrder()).andReturn(parents);
+
+    PluginConfig pluginConfig = createMock(PluginConfig.class);
+
+    expect(pluginConfigFactory.getFromProjectConfig(
+        projectState, "ItsTestName")).andReturn(pluginConfig).anyTimes();
+
+    expect(pluginConfig.getString("enabled")).andReturn(enabled).anyTimes();
+
+    PluginConfig pluginConfigWI = createMock(PluginConfig.class);
+
+    expect(pluginConfigFactory.getFromProjectConfigWithInheritance(
+        projectState, "ItsTestName")).andReturn(pluginConfigWI).anyTimes();
+
+    expect(pluginConfigWI.getBoolean("enabled", false))
+        .andReturn("true".equals(enabled)).anyTimes();
+
+    expect(pluginConfigWI.getStringList("branch")).andReturn(branches)
+        .anyTimes();
+  }
+
+  public void testIsEnabledRefNoParentNoBranchEnabled() {
+    String[] branches = {};
+    setupIsEnabled("true", null, branches);
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled("testProject", "refs/heads/testBranch"));
+  }
+
+  public void testIsEnabledRefNoParentNoBranchDisabled() {
+    String[] branches = {};
+    setupIsEnabled("false", null, branches);
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertFalse(itsConfig.isEnabled("testProject", "refs/heads/testBranch"));
+  }
+
+  public void testIsEnabledRefNoParentNoBranchEnforced() {
+    String[] branches = {};
+    setupIsEnabled("enforced", null, branches);
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled("testProject", "refs/heads/testBranch"));
+  }
+
+  public void testIsEnabledRefNoParentMatchingBranchEnabled() {
+    String[] branches = {"^refs/heads/test.*"};
+    setupIsEnabled("true", null, branches);
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled("testProject", "refs/heads/testBranch"));
+  }
+
+  public void testIsEnabledRefNoParentMatchingBranchDisabled() {
+    String[] branches = {"^refs/heads/test.*"};
+    setupIsEnabled("false", null, branches);
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertFalse(itsConfig.isEnabled("testProject", "refs/heads/testBranch"));
+  }
+
+  public void testIsEnabledRefNoParentMatchingBranchEnforced() {
+    String[] branches = {"^refs/heads/test.*"};
+    setupIsEnabled("enforced", null, branches);
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled("testProject", "refs/heads/testBranch"));
+  }
+
+  public void testIsEnabledRefNoParentNonMatchingBranchEnabled() {
+    String[] branches = {"^refs/heads/foo.*"};
+    setupIsEnabled("true", null, branches);
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertFalse(itsConfig.isEnabled("testProject", "refs/heads/testBranch"));
+  }
+
+  public void testIsEnabledRefNoParentNonMatchingBranchDisabled() {
+    String[] branches = {"^refs/heads/foo.*"};
+    setupIsEnabled("false", null, branches);
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertFalse(itsConfig.isEnabled("testProject", "refs/heads/testBranch"));
+  }
+
+  public void testIsEnabledRefNoParentNonMatchingBranchEnforced() {
+    String[] branches = {"^refs/heads/foo.*"};
+    setupIsEnabled("enforced", null, branches);
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertFalse(itsConfig.isEnabled("testProject", "refs/heads/testBranch"));
+  }
+
+  public void testIsEnabledRefNoParentMatchingBranchMiddleEnabled() {
+    String[] branches = {"^refs/heads/foo.*", "^refs/heads/test.*", "^refs/heads/baz.*"};
+    setupIsEnabled("true", null, branches);
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled("testProject", "refs/heads/testBranch"));
+  }
+
+  public void testIsEnabledRefNoParentMatchingBranchMiddleDisabled() {
+    String[] branches = {"^refs/heads/foo.*", "^refs/heads/test.*", "^refs/heads/baz.*"};
+    setupIsEnabled("false", null, branches);
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertFalse(itsConfig.isEnabled("testProject", "refs/heads/testBranch"));
+  }
+
+  public void testIsEnabledRefNoParentMatchingBranchMiddleEnforced() {
+    String[] branches = {"^refs/heads/foo.*", "^refs/heads/test.*", "^refs/heads/baz.*"};
+    setupIsEnabled("enforced", null, branches);
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled("testProject", "refs/heads/testBranch"));
+  }
+
+  public void testIsEnabledRefParentNoBranchEnabled() {
+    String[] branches = {};
+    setupIsEnabled("false", "true", branches);
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertFalse(itsConfig.isEnabled("testProject", "refs/heads/testBranch"));
+  }
+
+  public void testIsEnabledRefParentNoBranchDisabled() {
+    String[] branches = {};
+    setupIsEnabled("false", "false", branches);
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertFalse(itsConfig.isEnabled("testProject", "refs/heads/testBranch"));
+  }
+
+  public void testIsEnabledRefParentNoBranchEnforced() {
+    String[] branches = {};
+    setupIsEnabled("false", "enforced", branches);
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled("testProject", "refs/heads/testBranch"));
+  }
+
+  public void testIsEnabledEventNoBranches() {
+    String[] branches = {};
+    setupIsEnabled("true", null, branches);
+
+    PatchSetCreatedEvent event = new PatchSetCreatedEvent();
+    event.change = new ChangeAttribute();
+    event.change.project = "testProject";
+    event.change.branch = "testBranch";
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled(event));
+  }
+
+  public void testIsEnabledEventSingleBranchExact() {
+    String[] branches = {"refs/heads/testBranch"};
+    setupIsEnabled("true", null, branches);
+
+    PatchSetCreatedEvent event = new PatchSetCreatedEvent();
+    event.change = new ChangeAttribute();
+    event.change.project = "testProject";
+    event.change.branch = "testBranch";
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled(event));
+  }
+
+  public void testIsEnabledEventSingleBranchRegExp() {
+    String[] branches = {"^refs/heads/test.*"};
+    setupIsEnabled("true", null, branches);
+
+    PatchSetCreatedEvent event = new PatchSetCreatedEvent();
+    event.change = new ChangeAttribute();
+    event.change.project = "testProject";
+    event.change.branch = "testBranch";
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled(event));
+  }
+
+  public void testIsEnabledEventSingleBranchNonMatchingRegExp() {
+    String[] branches = {"^refs/heads/foo.*"};
+    setupIsEnabled("true", null, branches);
+
+    PatchSetCreatedEvent event = new PatchSetCreatedEvent();
+    event.change = new ChangeAttribute();
+    event.change.project = "testProject";
+    event.change.branch = "testBranch";
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertFalse(itsConfig.isEnabled(event));
+  }
+
+  public void testIsEnabledEventMultiBranchExact() {
+    String[] branches = {"refs/heads/foo", "refs/heads/testBranch"};
+    setupIsEnabled("true", null, branches);
+
+    PatchSetCreatedEvent event = new PatchSetCreatedEvent();
+    event.change = new ChangeAttribute();
+    event.change.project = "testProject";
+    event.change.branch = "testBranch";
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled(event));
+  }
+
+  public void testIsEnabledEventMultiBranchRegExp() {
+    String[] branches = {"^refs/heads/foo.*", "^refs/heads/test.*"};
+    setupIsEnabled("true", null, branches);
+
+    PatchSetCreatedEvent event = new PatchSetCreatedEvent();
+    event.change = new ChangeAttribute();
+    event.change.project = "testProject";
+    event.change.branch = "testBranch";
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled(event));
+  }
+
+  public void testIsEnabledEventMultiBranchMixedMatchExact() {
+    String[] branches = {"refs/heads/testBranch", "refs/heads/foo.*"};
+    setupIsEnabled("true", null, branches);
+
+    PatchSetCreatedEvent event = new PatchSetCreatedEvent();
+    event.change = new ChangeAttribute();
+    event.change.project = "testProject";
+    event.change.branch = "testBranch";
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled(event));
+  }
+
+public void testIsEnabledEventMultiBranchMixedMatchRegExp() {
+    String[] branches = {"refs/heads/foo", "^refs/heads/test.*"};
+    setupIsEnabled("true", null, branches);
+
+    PatchSetCreatedEvent event = new PatchSetCreatedEvent();
+    event.change = new ChangeAttribute();
+    event.change.project = "testProject";
+    event.change.branch = "testBranch";
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled(event));
+  }
+
+  public void testIsEnabledEventDisabled() {
+    String[] branches = {"^refs/heads/testBranch"};
+    setupIsEnabled("false", null, branches);
+
+    PatchSetCreatedEvent event = new PatchSetCreatedEvent();
+    event.change = new ChangeAttribute();
+    event.change.project = "testProject";
+    event.change.branch = "testBranch";
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertFalse(itsConfig.isEnabled(event));
+  }
+
+  public void testIsEnabledCommentAddedEvent() {
+    String[] branches = {};
+    setupIsEnabled("true", null, branches);
+
+    CommentAddedEvent event = new CommentAddedEvent();
+    event.change = new ChangeAttribute();
+    event.change.project = "testProject";
+    event.change.branch = "testBranch";
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled(event));
+  }
+
+  public void testIsEnabledChangeMergedEvent() {
+    String[] branches = {};
+    setupIsEnabled("true", null, branches);
+
+    ChangeMergedEvent event = new ChangeMergedEvent();
+    event.change = new ChangeAttribute();
+    event.change.project = "testProject";
+    event.change.branch = "testBranch";
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled(event));
+  }
+
+  public void testIsEnabledChangeAbandonedEvent() {
+    String[] branches = {};
+    setupIsEnabled("true", null, branches);
+
+    ChangeAbandonedEvent event = new ChangeAbandonedEvent();
+    event.change = new ChangeAttribute();
+    event.change.project = "testProject";
+    event.change.branch = "testBranch";
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled(event));
+  }
+
+  public void testIsEnabledChangeRestoredEvent() {
+    String[] branches = {};
+    setupIsEnabled("true", null, branches);
+
+    ChangeRestoredEvent event = new ChangeRestoredEvent();
+    event.change = new ChangeAttribute();
+    event.change.project = "testProject";
+    event.change.branch = "testBranch";
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled(event));
+  }
+
+  public void testIsEnabledDraftPublishedEvent() {
+    String[] branches = {};
+    setupIsEnabled("true", null, branches);
+
+    DraftPublishedEvent event = new DraftPublishedEvent();
+    event.change = new ChangeAttribute();
+    event.change.project = "testProject";
+    event.change.branch = "testBranch";
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled(event));
+  }
+
+  public void testIsEnabledRefUpdatedEvent() {
+    String[] branches = {};
+    setupIsEnabled("true", null, branches);
+
+    RefUpdatedEvent event = new RefUpdatedEvent();
+    event.refUpdate = new RefUpdateAttribute();
+    event.refUpdate.project = "testProject";
+    event.refUpdate.refName = "refs/heads/testBranch";
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertTrue(itsConfig.isEnabled(event));
+  }
+
+  public void testIsEnabledUnknownEvent() {
+    Event event = new Event("foo") {};
+
+    ItsConfig itsConfig = createItsConfig();
+
+    replayMocks();
+
+    assertFalse(itsConfig.isEnabled(event));
+    assertLogMessageContains("not recognised and ignored");
+  }
+
+  public void testGetIssuePatternNullMatch() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn(null).atLeastOnce();
+    expect(serverConfig.getString("commentlink", "ItsTestName", "match"))
+        .andReturn(null).atLeastOnce();
+
+    replayMocks();
+
+    assertNull("Pattern for null match is not null",
+        itsConfig.getIssuePattern());
+  }
+
+  public void testGetIssuePatternNullMatchWCommentLink() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn("foo").atLeastOnce();
+    expect(serverConfig.getString("commentlink", "foo", "match"))
+        .andReturn(null).atLeastOnce();
+
+    replayMocks();
+
+    assertNull("Pattern for null match is not null",
+        itsConfig.getIssuePattern());
+  }
+
+  public void testGetIssuePattern() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn(null).atLeastOnce();
+    expect(serverConfig.getString("commentlink", "ItsTestName", "match"))
+        .andReturn("TestPattern").atLeastOnce();
+
+    replayMocks();
+
+    assertEquals("Expected and generated pattern are not equal",
+        "TestPattern", itsConfig.getIssuePattern().pattern());
+  }
+
+  public void testGetIssuePatternWCommentLink() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn("foo").atLeastOnce();
+    expect(serverConfig.getString("commentlink", "foo", "match"))
+        .andReturn("TestPattern").atLeastOnce();
+
+    replayMocks();
+
+    assertEquals("Expected and generated pattern are not equal",
+        "TestPattern", itsConfig.getIssuePattern().pattern());
+
+  }
+
+  public void testGetIssuePatternGroupIndexGroupDefault() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn(null).atLeastOnce();
+    expect(serverConfig.getString("commentlink", "ItsTestName", "match"))
+        .andReturn("(foo)(bar)(baz)").atLeastOnce();
+    expect(serverConfig.getInt("ItsTestName", "commentlinkGroupIndex", 1))
+        .andReturn(1).atLeastOnce();
+
+    replayMocks();
+
+    assertEquals("Expected and actual group index do not match",
+        1, itsConfig.getIssuePatternGroupIndex());
+  }
+
+  public void testGetIssuePatternGroupIndexGroupDefaultGroupless() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn(null).atLeastOnce();
+    expect(serverConfig.getString("commentlink", "ItsTestName", "match"))
+        .andReturn("foo").atLeastOnce();
+    expect(serverConfig.getInt("ItsTestName", "commentlinkGroupIndex", 1))
+        .andReturn(1).atLeastOnce();
+
+    replayMocks();
+
+    assertEquals("Expected and actual group index do not match",
+        0, itsConfig.getIssuePatternGroupIndex());
+  }
+
+  public void testGetIssuePatternGroupIndexGroup1() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn(null).atLeastOnce();
+    expect(serverConfig.getString("commentlink", "ItsTestName", "match"))
+        .andReturn("(foo)(bar)(baz)").atLeastOnce();
+    expect(serverConfig.getInt("ItsTestName", "commentlinkGroupIndex", 1))
+        .andReturn(1).atLeastOnce();
+
+    replayMocks();
+
+    assertEquals("Expected and actual group index do not match",
+        1, itsConfig.getIssuePatternGroupIndex());
+  }
+
+  public void testGetIssuePatternGroupIndexGroup3() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn(null).atLeastOnce();
+    expect(serverConfig.getString("commentlink", "ItsTestName", "match"))
+        .andReturn("(foo)(bar)(baz)").atLeastOnce();
+    expect(serverConfig.getInt("ItsTestName", "commentlinkGroupIndex", 1))
+        .andReturn(3).atLeastOnce();
+
+    replayMocks();
+
+    assertEquals("Expected and actual group index do not match",
+        3, itsConfig.getIssuePatternGroupIndex());
+  }
+
+  public void testGetIssuePatternGroupIndexGroupTooHigh() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn(null).atLeastOnce();
+    expect(serverConfig.getString("commentlink", "ItsTestName", "match"))
+        .andReturn("(foo)(bar)(baz)").atLeastOnce();
+    expect(serverConfig.getInt("ItsTestName", "commentlinkGroupIndex", 1))
+        .andReturn(5).atLeastOnce();
+
+    replayMocks();
+
+    assertEquals("Expected and actual group index do not match",
+        1, itsConfig.getIssuePatternGroupIndex());
+  }
+
+  public void testGetIssuePatternGroupIndexGroupTooHighGroupless() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn(null).atLeastOnce();
+    expect(serverConfig.getString("commentlink", "ItsTestName", "match"))
+        .andReturn("foo").atLeastOnce();
+    expect(serverConfig.getInt("ItsTestName", "commentlinkGroupIndex", 1))
+        .andReturn(5).atLeastOnce();
+
+    replayMocks();
+
+    assertEquals("Expected and actual group index do not match",
+        0, itsConfig.getIssuePatternGroupIndex());
+  }
+
+  public void testGetItsAssociationPolicyOptional() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn(null).atLeastOnce();
+    expect(serverConfig.getEnum("commentlink", "ItsTestName", "association",
+        ItsAssociationPolicy.OPTIONAL))
+        .andReturn(ItsAssociationPolicy.OPTIONAL)
+        .atLeastOnce();
+
+    replayMocks();
+
+    assertEquals("Expected and generated associated policy do not match",
+        ItsAssociationPolicy.OPTIONAL, itsConfig.getItsAssociationPolicy());
+  }
+
+  public void testGetItsAssociationPolicyOptionalWCommentLink() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn("foo").atLeastOnce();
+    expect(serverConfig.getEnum("commentlink", "foo", "association",
+        ItsAssociationPolicy.OPTIONAL))
+        .andReturn(ItsAssociationPolicy.OPTIONAL)
+        .atLeastOnce();
+
+    replayMocks();
+
+    assertEquals("Expected and generated associated policy do not match",
+        ItsAssociationPolicy.OPTIONAL, itsConfig.getItsAssociationPolicy());
+  }
+
+  public void testGetItsAssociationPolicySuggested() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn(null).atLeastOnce();
+    expect(serverConfig.getEnum("commentlink", "ItsTestName", "association",
+        ItsAssociationPolicy.OPTIONAL))
+        .andReturn(ItsAssociationPolicy.SUGGESTED)
+        .atLeastOnce();
+
+    replayMocks();
+
+    assertEquals("Expected and generated associated policy do not match",
+        ItsAssociationPolicy.SUGGESTED, itsConfig.getItsAssociationPolicy());
+  }
+
+  public void testGetItsAssociationPolicySuggestedWCommentLink() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn("foo").atLeastOnce();
+    expect(serverConfig.getEnum("commentlink", "foo", "association",
+        ItsAssociationPolicy.OPTIONAL))
+        .andReturn(ItsAssociationPolicy.SUGGESTED)
+        .atLeastOnce();
+
+    replayMocks();
+
+    assertEquals("Expected and generated associated policy do not match",
+        ItsAssociationPolicy.SUGGESTED, itsConfig.getItsAssociationPolicy());
+  }
+
+  public void testGetItsAssociationPolicyMandatory() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn(null).atLeastOnce();
+    expect(serverConfig.getEnum("commentlink", "ItsTestName", "association",
+        ItsAssociationPolicy.OPTIONAL))
+        .andReturn(ItsAssociationPolicy.MANDATORY)
+        .atLeastOnce();
+
+    replayMocks();
+
+    assertEquals("Expected and generated associated policy do not match",
+        ItsAssociationPolicy.MANDATORY, itsConfig.getItsAssociationPolicy());
+  }
+
+  public void testGetItsAssociationPolicyMandatoryWCommentLink() {
+    ItsConfig itsConfig = createItsConfig();
+
+    expect(serverConfig.getString("ItsTestName", null, "commentlink"))
+        .andReturn("foo").atLeastOnce();
+    expect(serverConfig.getEnum("commentlink", "foo", "association",
+        ItsAssociationPolicy.OPTIONAL))
+        .andReturn(ItsAssociationPolicy.MANDATORY)
+        .atLeastOnce();
+
+    replayMocks();
+
+    assertEquals("Expected and generated associated policy do not match",
+        ItsAssociationPolicy.MANDATORY, itsConfig.getItsAssociationPolicy());
+  }
+
+  private ItsConfig createItsConfig() {
+    return injector.getInstance(ItsConfig.class);
+  }
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    injector = Guice.createInjector(new TestModule());
+  }
+
+  private class TestModule extends FactoryModule {
+    @Override
+    protected void configure() {
+      projectCache = createMock(ProjectCache.class);
+      bind(ProjectCache.class).toInstance(projectCache);
+
+      pluginConfigFactory = createMock(PluginConfigFactory.class);
+      bind(PluginConfigFactory.class).toInstance(pluginConfigFactory);
+
+      bind(String.class).annotatedWith(PluginName.class)
+        .toInstance("ItsTestName");
+
+      serverConfig = createMock(Config.class);
+      bind(Config.class).annotatedWith(GerritServerConfig.class)
+          .toInstance(serverConfig);
+    }
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/LoggingMockingTestCase.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/testutil/LoggingMockingTestCase.java
similarity index 96%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/LoggingMockingTestCase.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/testutil/LoggingMockingTestCase.java
index 4878c40..2be4610 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/LoggingMockingTestCase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/testutil/LoggingMockingTestCase.java
@@ -12,16 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.testutil;
+package com.googlesource.gerrit.plugins.its.base.testutil;
 
 import com.google.common.collect.Lists;
-import com.googlesource.gerrit.plugins.hooks.testutil.log.LogUtil;
 
+import com.googlesource.gerrit.plugins.its.base.testutil.log.LogUtil;
+
+import org.apache.log4j.Level;
 import org.apache.log4j.spi.LoggingEvent;
 import org.junit.After;
 
 import java.util.Iterator;
-import org.apache.log4j.Level;
 
 public abstract class LoggingMockingTestCase extends MockingTestCase {
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/MockingTestCase.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/testutil/MockingTestCase.java
similarity index 93%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/MockingTestCase.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/testutil/MockingTestCase.java
index 819b138..dbb3d06 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/MockingTestCase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/testutil/MockingTestCase.java
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.testutil;
+package com.googlesource.gerrit.plugins.its.base.testutil;
 
 import junit.framework.TestCase;
 
+import org.easymock.Capture;
 import org.easymock.EasyMock;
 import org.easymock.IMocksControl;
 import org.junit.After;
@@ -136,8 +137,8 @@
       usePowerMock = PowerMockRunner.class.isAssignableFrom(runWith.value());
     }
 
-    mocks = new ArrayList<Object>();
-    mockControls = new ArrayList<IMocksControl>();
+    mocks = new ArrayList<>();
+    mockControls = new ArrayList<>();
     mocksReplayed = false;
   }
 
@@ -150,4 +151,13 @@
     // subclasses open.
     verifyMocks();
   }
+
+  /**
+   * Create a new Capture.
+   *
+   * @return The created Capture.
+   */
+  protected final <T> Capture<T> createCapture() {
+    return EasyMock.newCapture();
+  }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/CollectionAppender.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/testutil/log/CollectionAppender.java
similarity index 93%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/CollectionAppender.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/testutil/log/CollectionAppender.java
index f63c7c3..a533f8c 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/CollectionAppender.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/testutil/log/CollectionAppender.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.testutil.log;
+package com.googlesource.gerrit.plugins.its.base.testutil.log;
 
 import com.google.common.collect.Lists;
 
@@ -29,7 +29,7 @@
   private Collection<LoggingEvent> events;
 
   public CollectionAppender() {
-    events = new LinkedList<LoggingEvent>();
+    events = new LinkedList<>();
   }
 
   public CollectionAppender(Collection<LoggingEvent> events) {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/LogUtil.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/testutil/log/LogUtil.java
similarity index 94%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/LogUtil.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/testutil/log/LogUtil.java
index 57d98c7..c5a6cb3 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/LogUtil.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/testutil/log/LogUtil.java
@@ -12,13 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.hooks.testutil.log;
+package com.googlesource.gerrit.plugins.its.base.testutil.log;
 
 import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
 import org.apache.log4j.spi.LoggingEvent;
 
-
 import java.util.Collection;
 
 public class LogUtil {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/util/IssueExtractorTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/util/IssueExtractorTest.java
similarity index 84%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/util/IssueExtractorTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/util/IssueExtractorTest.java
index e400a95..b79e764 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/util/IssueExtractorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/util/IssueExtractorTest.java
@@ -11,73 +11,48 @@
 // 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.util;
+package com.googlesource.gerrit.plugins.its.base.util;
 
 import static org.easymock.EasyMock.expect;
 
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.eclipse.jgit.lib.Config;
-import org.junit.runner.RunWith;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
-import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.PatchSetAccess;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.FactoryModule;
-import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
+import com.googlesource.gerrit.plugins.its.base.its.ItsConfig;
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
 
 @RunWith(PowerMockRunner.class)
 @PrepareForTest({PatchSet.class,RevId.class})
 public class IssueExtractorTest extends LoggingMockingTestCase {
   private Injector injector;
-  private Config serverConfig;
+  private ItsConfig itsConfig;
   private CommitMessageFetcher commitMessageFetcher;
   private ReviewDb db;
 
-  public void testPatternNullMatch() {
-    IssueExtractor issueExtractor = injector.getInstance(IssueExtractor.class);
-
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-        .andReturn(null).atLeastOnce();
-
-    replayMocks();
-
-    assertNull("Pattern for null match is not null",
-        issueExtractor.getPattern());
-  }
-
-  public void testPattern() {
-    IssueExtractor issueExtractor = injector.getInstance(IssueExtractor.class);
-
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-        .andReturn("TestPattern").atLeastOnce();
-
-    replayMocks();
-
-    assertEquals("Expected and generated pattern are not equal",
-        "TestPattern", issueExtractor.getPattern().pattern());
-  }
-
   public void testIssueIdsNullPattern() {
     IssueExtractor issueExtractor = injector.getInstance(IssueExtractor.class);
 
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-        .andReturn(null).atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(null)
+        .atLeastOnce();
 
     replayMocks();
 
@@ -87,8 +62,10 @@
 
   public void testIssueIdsNoMatch() {
     IssueExtractor issueExtractor = injector.getInstance(IssueExtractor.class);
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-        .andReturn("bug#(\\d+)").atLeastOnce();
+
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     replayMocks();
 
@@ -98,10 +75,27 @@
     assertLogMessageContains("Matching");
   }
 
+  public void testIssueIdsEmptyGroup() {
+    IssueExtractor issueExtractor = injector.getInstance(IssueExtractor.class);
+
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(X*)(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
+
+    replayMocks();
+
+    String ret[] = issueExtractor.getIssueIds("bug#4711");
+    assertEquals("Number of found ids do not match", 0, ret.length);
+
+    assertLogMessageContains("Matching");
+  }
+
   public void testIssueIdsFullMatch() {
     IssueExtractor issueExtractor = injector.getInstance(IssueExtractor.class);
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-        .andReturn("bug#(\\d+)").atLeastOnce();
+
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     replayMocks();
 
@@ -114,8 +108,10 @@
 
   public void testIssueIdsMatch() {
     IssueExtractor issueExtractor = injector.getInstance(IssueExtractor.class);
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-        .andReturn("bug#(\\d+)").atLeastOnce();
+
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     replayMocks();
 
@@ -128,8 +124,10 @@
 
   public void testIssueIdsGrouplessMatch() {
     IssueExtractor issueExtractor = injector.getInstance(IssueExtractor.class);
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-        .andReturn("bug#\\d+").atLeastOnce();
+
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#\\d+"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(0).atLeastOnce();
 
     replayMocks();
 
@@ -140,10 +138,12 @@
     assertLogMessageContains("Matching");
   }
 
-  public void testIssueIdsMultiGroupMatch() {
+  public void testIssueIdsMultiGroupMatchGroup1() {
     IssueExtractor issueExtractor = injector.getInstance(IssueExtractor.class);
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-        .andReturn("bug#(\\d)(\\d+)").atLeastOnce();
+
+    expect(itsConfig.getIssuePattern())
+        .andReturn(Pattern.compile("bug#(\\d)(\\d+)")).atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     replayMocks();
 
@@ -154,10 +154,28 @@
     assertLogMessageContains("Matching");
   }
 
+  public void testIssueIdsMultiGroupMatchGroup2() {
+    IssueExtractor issueExtractor = injector.getInstance(IssueExtractor.class);
+
+    expect(itsConfig.getIssuePattern())
+        .andReturn(Pattern.compile("bug#(\\d)(\\d+)")).atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(2).atLeastOnce();
+
+    replayMocks();
+
+    String ret[] = issueExtractor.getIssueIds("Foo bug#4711 bar");
+    assertEquals("Number of found ids do not match", 1, ret.length);
+    assertEquals("Found issue id does not match", "711", ret[0]);
+
+    assertLogMessageContains("Matching");
+  }
+
   public void testIssueIdsMulipleMatches() {
     IssueExtractor issueExtractor = injector.getInstance(IssueExtractor.class);
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-        .andReturn("bug#(\\d+)").atLeastOnce();
+
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     replayMocks();
 
@@ -173,8 +191,10 @@
 
   public void testIssueIdsMulipleMatchesWithDuplicates() {
     IssueExtractor issueExtractor = injector.getInstance(IssueExtractor.class);
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-        .andReturn("bug#(\\d+)").atLeastOnce();
+
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     replayMocks();
 
@@ -190,8 +210,9 @@
   }
 
   public void testIssueIdsCommitSingleIssue() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn(
@@ -217,8 +238,9 @@
   }
 
   public void testIssueIdsCommitMultipleIssues() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn(
@@ -245,8 +267,9 @@
   }
 
   public void testIssueIdsCommitMultipleIssuesMultipleTimes() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn(
@@ -273,8 +296,9 @@
   }
 
   public void testIssueIdsCommitSingleIssueBody() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn(
@@ -303,8 +327,9 @@
   }
 
   public void testIssueIdsCommitSingleIssueFooter() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn(
@@ -334,8 +359,9 @@
   }
 
   public void testIssueIdsCommitMultipleIssuesFooter() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn(
@@ -374,8 +400,9 @@
   }
 
   public void testIssueIdsCommitDifferentParts() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn(
@@ -408,8 +435,9 @@
   }
 
   public void testIssueIdsCommitDifferentPartsEmptySubject() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn(
@@ -441,8 +469,9 @@
   }
 
   public void testIssueIdsCommitDifferentPartsLinePastFooter() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn(
@@ -475,8 +504,9 @@
   }
 
   public void testIssueIdsCommitDifferentPartsLinesPastFooter() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn(
@@ -510,8 +540,9 @@
   }
 
   public void testIssueIdsCommitDifferentPartsNoFooter() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn(
@@ -537,8 +568,9 @@
   }
 
   public void testIssueIdsCommitDifferentPartsNoFooterTrailingLine() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn(
@@ -564,8 +596,9 @@
   }
 
   public void testIssueIdsCommitDifferentPartsNoFooterTrailingLines() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn(
@@ -592,8 +625,9 @@
   }
 
   public void testIssueIdsCommitEmpty() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn("");
@@ -613,8 +647,9 @@
   }
 
   public void testIssueIdsCommitBlankLine() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn("\n");
@@ -632,8 +667,9 @@
   }
 
   public void testIssueIdsCommitBlankLines() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn("\n\n");
@@ -651,8 +687,9 @@
   }
 
   public void testIssueIdsCommitMoreBlankLines() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn("\n\n\n");
@@ -670,8 +707,9 @@
   }
 
   public void testIssueIdsCommitMixed() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn(
@@ -710,8 +748,9 @@
   }
 
   public void testIssueIdsCommitWAddedEmptyFirst() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     expect(commitMessageFetcher.fetchGuarded("testProject",
         "1234567891123456789212345678931234567894")).andReturn("");
@@ -732,8 +771,9 @@
   }
 
   public void testIssueIdsCommitWAddedSingleSubjectIssueFirst() {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     Change.Id changeId = createMock(Change.Id.class);
 
@@ -768,8 +808,9 @@
 
   public void testIssueIdsCommitWAddedSingleSubjectIssueSecondEmpty()
       throws OrmException {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     Change.Id changeId = createMock(Change.Id.class);
 
@@ -830,8 +871,9 @@
 
   public void testIssueIdsCommitWAddedSingleSubjectIssueSecondSame()
       throws OrmException {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     Change.Id changeId = createMock(Change.Id.class);
 
@@ -891,8 +933,9 @@
 
   public void testIssueIdsCommitWAddedSingleSubjectIssueSecondBody()
       throws OrmException {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     Change.Id changeId = createMock(Change.Id.class);
 
@@ -953,8 +996,9 @@
 
   public void testIssueIdsCommitWAddedSingleSubjectIssueSecondFooter()
       throws OrmException {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     Change.Id changeId = createMock(Change.Id.class);
 
@@ -1017,8 +1061,9 @@
 
   public void testIssueIdsCommitWAddedSubjectFooter()
       throws OrmException {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     Change.Id changeId = createMock(Change.Id.class);
 
@@ -1085,8 +1130,9 @@
 
   public void testIssueIdsCommitWAddedMultiple()
       throws OrmException {
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-    .andReturn("bug#(\\d+)").atLeastOnce();
+    expect(itsConfig.getIssuePattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+        .atLeastOnce();
+    expect(itsConfig.getIssuePatternGroupIndex()).andReturn(1).atLeastOnce();
 
     Change.Id changeId = createMock(Change.Id.class);
 
@@ -1164,12 +1210,8 @@
   private class TestModule extends FactoryModule {
     @Override
     protected void configure() {
-      bind(String.class).annotatedWith(PluginName.class)
-          .toInstance("ItsTestName");
-
-      serverConfig = createMock(Config.class);
-      bind(Config.class).annotatedWith(GerritServerConfig.class)
-          .toInstance(serverConfig);
+      itsConfig = createMock(ItsConfig.class);
+      bind(ItsConfig.class).toInstance(itsConfig);
 
       commitMessageFetcher = createMock(CommitMessageFetcher.class);
       bind(CommitMessageFetcher.class).toInstance(commitMessageFetcher);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/util/PropertyAttributeExtractorTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyAttributeExtractorTest.java
similarity index 98%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/util/PropertyAttributeExtractorTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyAttributeExtractorTest.java
index ffc431b..52e9155 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/util/PropertyAttributeExtractorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyAttributeExtractorTest.java
@@ -11,16 +11,14 @@
 // 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.util;
+package com.googlesource.gerrit.plugins.its.base.util;
 
 import static org.easymock.EasyMock.expect;
 
-import java.util.Set;
-
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.reviewdb.client.Change.Status;
-import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.data.AccountAttribute;
 import com.google.gerrit.server.data.ApprovalAttribute;
 import com.google.gerrit.server.data.ChangeAttribute;
@@ -28,8 +26,11 @@
 import com.google.gerrit.server.data.RefUpdateAttribute;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
-import com.googlesource.gerrit.plugins.hooks.workflow.Property;
+
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+import com.googlesource.gerrit.plugins.its.base.workflow.Property;
+
+import java.util.Set;
 
 public class PropertyAttributeExtractorTest extends LoggingMockingTestCase {
   private Injector injector;
@@ -426,6 +427,7 @@
     assertEquals("Properties do not match", expected, actual);
   }
 
+  @Override
   public void setUp() throws Exception {
     super.setUp();
     injector = Guice.createInjector(new TestModule());
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/util/PropertyExtractorTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractorTest.java
similarity index 95%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/util/PropertyExtractorTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractorTest.java
index 7657f24..de432bb 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/util/PropertyExtractorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractorTest.java
@@ -11,15 +11,16 @@
 // 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.util;
+package com.googlesource.gerrit.plugins.its.base.util;
 
 import static org.easymock.EasyMock.expect;
 
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.data.AccountAttribute;
 import com.google.gerrit.server.data.ApprovalAttribute;
 import com.google.gerrit.server.data.ChangeAttribute;
@@ -36,8 +37,8 @@
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
-import com.googlesource.gerrit.plugins.hooks.workflow.Property;
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+import com.googlesource.gerrit.plugins.its.base.workflow.Property;
 
 import java.util.HashMap;
 import java.util.Set;
@@ -55,7 +56,7 @@
 
     Property property1 = createMock(Property.class);
     expect(propertyFactory.create("event", "com.googlesource.gerrit.plugins." +
-        "hooks.util.PropertyExtractorTest$DummyEvent"))
+        "its.base.util.PropertyExtractorTest$DummyEvent"))
         .andReturn(property1);
 
     replayMocks();
@@ -377,6 +378,10 @@
     PropertyExtractor propertyExtractor = injector.getInstance(
         PropertyExtractor.class);
 
+    Property propertyItsName = createMock(Property.class);
+    expect(propertyFactory.create("its-name", "ItsTestName"))
+        .andReturn(propertyItsName).anyTimes();
+
     Property propertyEvent = createMock(Property.class);
     expect(propertyFactory.create("event", "com.google.gerrit.server.events." +
         className)).andReturn(propertyEvent);
@@ -423,6 +428,7 @@
 
     Set<Set<Property>> expected = Sets.newHashSet();
     Set<Property> properties = Sets.newHashSet();
+    properties.add(propertyItsName);
     properties.add(propertyEvent);
     properties.add(propertyEventType);
     properties.add(propertyAssociationAnywhere);
@@ -432,6 +438,7 @@
     expected.add(properties);
 
     properties = Sets.newHashSet();
+    properties.add(propertyItsName);
     properties.add(propertyEvent);
     properties.add(propertyEventType);
     properties.add(propertyAssociationAnywhere);
@@ -442,6 +449,7 @@
     assertEquals("Properties do not match", expected, actual);
   }
 
+  @Override
   public void setUp() throws Exception {
     super.setUp();
     injector = Guice.createInjector(new TestModule());
@@ -450,6 +458,9 @@
   private class TestModule extends FactoryModule {
     @Override
     protected void configure() {
+      bind(String.class).annotatedWith(PluginName.class)
+          .toInstance("ItsTestName");
+
       issueExtractor = createMock(IssueExtractor.class);
       bind(IssueExtractor.class).toInstance(issueExtractor);
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateCommentTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateCommentTest.java
similarity index 82%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateCommentTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateCommentTest.java
index 7c65938..5ddc514 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateCommentTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateCommentTest.java
@@ -11,45 +11,42 @@
 // 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.validation;
+package com.googlesource.gerrit.plugins.its.base.validation;
 
 import static org.easymock.EasyMock.expect;
 
-import java.io.IOException;
-import java.util.List;
-import java.util.regex.Pattern;
-
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.transport.ReceiveCommand;
-
-import org.junit.runner.RunWith;
-
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
 import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.FactoryModule;
-import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.events.CommitReceivedEvent;
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidationMessage;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 
-import com.googlesource.gerrit.plugins.hooks.its.ItsConfig;
-import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
-import com.googlesource.gerrit.plugins.hooks.util.IssueExtractor;
+import com.googlesource.gerrit.plugins.its.base.its.ItsConfig;
+import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+import com.googlesource.gerrit.plugins.its.base.util.IssueExtractor;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.regex.Pattern;
 
 @RunWith(PowerMockRunner.class)
 @PrepareForTest({RevCommit.class})
 public class ItsValidateCommentTest extends LoggingMockingTestCase {
   private Injector injector;
-  private Config serverConfig;
   private IssueExtractor issueExtractor;
   private ItsFacade itsFacade;
+  private ItsConfig itsConfig;
+
   private Project project = new Project(new Project.NameKey("myProject"));
 
   public void testOptional() throws CommitValidationException {
@@ -59,9 +56,10 @@
     RevCommit commit = createMock(RevCommit.class);
     CommitReceivedEvent event = new CommitReceivedEvent(command, project, null,
         commit, null);
-    expect(serverConfig.getEnum("commentLink", "ItsTestName", "association",
-        ItsAssociationPolicy.OPTIONAL)).andReturn(
-            ItsAssociationPolicy.OPTIONAL).atLeastOnce();
+
+    expect(itsConfig.getItsAssociationPolicy())
+        .andReturn(ItsAssociationPolicy.OPTIONAL).atLeastOnce();
+
     replayMocks();
 
     ret = ivc.onCommitReceived(event);
@@ -76,11 +74,9 @@
     RevCommit commit = createMock(RevCommit.class);
     CommitReceivedEvent event = new CommitReceivedEvent(command, project, null,
         commit, null);
-    expect(serverConfig.getEnum("commentLink", "ItsTestName", "association",
-        ItsAssociationPolicy.OPTIONAL)).andReturn(
-            ItsAssociationPolicy.SUGGESTED).atLeastOnce();
-    expect(serverConfig.getString("commentLink", "ItsTestName", "match"))
-        .andReturn("TestPattern").anyTimes();
+
+    expect(itsConfig.getItsAssociationPolicy())
+        .andReturn(ItsAssociationPolicy.SUGGESTED).atLeastOnce();
     expect(commit.getFullMessage()).andReturn("TestMessage").atLeastOnce();
     expect(commit.getId()).andReturn(commit).anyTimes();
     expect(commit.getName()).andReturn("TestCommit").anyTimes();
@@ -97,15 +93,15 @@
         "issue'",ret.get(0).getMessage().contains("Missing issue"));
   }
 
-  public void testMandatoryNonMatching() throws CommitValidationException {
+  public void testMandatoryNonMatching() {
     ItsValidateComment ivc = injector.getInstance(ItsValidateComment.class);
     ReceiveCommand command = createMock(ReceiveCommand.class);
     RevCommit commit = createMock(RevCommit.class);
     CommitReceivedEvent event = new CommitReceivedEvent(command, project, null,
         commit, null);
-    expect(serverConfig.getEnum("commentLink", "ItsTestName", "association",
-        ItsAssociationPolicy.OPTIONAL)).andReturn(
-            ItsAssociationPolicy.MANDATORY).atLeastOnce();
+
+    expect(itsConfig.getItsAssociationPolicy())
+        .andReturn(ItsAssociationPolicy.MANDATORY).atLeastOnce();
     expect(commit.getFullMessage()).andReturn("TestMessage").atLeastOnce();
     expect(commit.getId()).andReturn(commit).anyTimes();
     expect(commit.getName()).andReturn("TestCommit").anyTimes();
@@ -132,9 +128,8 @@
     RevCommit commit = createMock(RevCommit.class);
     CommitReceivedEvent event = new CommitReceivedEvent(command, project, null,
         commit, null);
-    expect(serverConfig.getEnum("commentLink", "ItsTestName", "association",
-        ItsAssociationPolicy.OPTIONAL)).andReturn(
-            ItsAssociationPolicy.SUGGESTED).atLeastOnce();
+    expect(itsConfig.getItsAssociationPolicy())
+        .andReturn(ItsAssociationPolicy.SUGGESTED).atLeastOnce();
     expect(commit.getFullMessage()).andReturn("bug#4711").atLeastOnce();
     expect(commit.getId()).andReturn(commit).anyTimes();
     expect(commit.getName()).andReturn("TestCommit").anyTimes();
@@ -157,9 +152,9 @@
     RevCommit commit = createMock(RevCommit.class);
     CommitReceivedEvent event = new CommitReceivedEvent(command, project, null,
         commit, null);
-    expect(serverConfig.getEnum("commentLink", "ItsTestName", "association",
-        ItsAssociationPolicy.OPTIONAL)).andReturn(
-            ItsAssociationPolicy.MANDATORY).atLeastOnce();
+
+    expect(itsConfig.getItsAssociationPolicy())
+      .andReturn(ItsAssociationPolicy.MANDATORY).atLeastOnce();
     expect(commit.getFullMessage()).andReturn("bug#4711").atLeastOnce();
     expect(commit.getId()).andReturn(commit).anyTimes();
     expect(commit.getName()).andReturn("TestCommit").anyTimes();
@@ -182,9 +177,9 @@
     RevCommit commit = createMock(RevCommit.class);
     CommitReceivedEvent event = new CommitReceivedEvent(command, project, null,
         commit, null);
-    expect(serverConfig.getEnum("commentLink", "ItsTestName", "association",
-        ItsAssociationPolicy.OPTIONAL)).andReturn(
-            ItsAssociationPolicy.SUGGESTED).atLeastOnce();
+
+    expect(itsConfig.getItsAssociationPolicy())
+        .andReturn(ItsAssociationPolicy.SUGGESTED).atLeastOnce();
     expect(commit.getFullMessage()).andReturn("bug#4711").atLeastOnce();
     expect(commit.getId()).andReturn(commit).anyTimes();
     expect(commit.getName()).andReturn("TestCommit").anyTimes();
@@ -205,15 +200,15 @@
   }
 
   public void testMandatoryMatchingSingleNonExisting()
-      throws CommitValidationException, IOException {
+      throws IOException {
     ItsValidateComment ivc = injector.getInstance(ItsValidateComment.class);
     ReceiveCommand command = createMock(ReceiveCommand.class);
     RevCommit commit = createMock(RevCommit.class);
     CommitReceivedEvent event = new CommitReceivedEvent(command, project, null,
         commit, null);
-    expect(serverConfig.getEnum("commentLink", "ItsTestName", "association",
-        ItsAssociationPolicy.OPTIONAL)).andReturn(
-            ItsAssociationPolicy.MANDATORY).atLeastOnce();
+
+    expect(itsConfig.getItsAssociationPolicy())
+        .andReturn(ItsAssociationPolicy.MANDATORY).atLeastOnce();
     expect(commit.getFullMessage()).andReturn("bug#4711").atLeastOnce();
     expect(commit.getId()).andReturn(commit).anyTimes();
     expect(commit.getName()).andReturn("TestCommit").anyTimes();
@@ -240,9 +235,9 @@
     RevCommit commit = createMock(RevCommit.class);
     CommitReceivedEvent event = new CommitReceivedEvent(command, project, null,
         commit, null);
-    expect(serverConfig.getEnum("commentLink", "ItsTestName", "association",
-        ItsAssociationPolicy.OPTIONAL)).andReturn(
-            ItsAssociationPolicy.SUGGESTED).atLeastOnce();
+
+    expect(itsConfig.getItsAssociationPolicy())
+        .andReturn(ItsAssociationPolicy.SUGGESTED).atLeastOnce();
     expect(commit.getFullMessage()).andReturn("bug#4711, bug#42")
         .atLeastOnce();
     expect(commit.getId()).andReturn(commit).anyTimes();
@@ -267,9 +262,9 @@
     RevCommit commit = createMock(RevCommit.class);
     CommitReceivedEvent event = new CommitReceivedEvent(command, project, null,
         commit, null);
-    expect(serverConfig.getEnum("commentLink", "ItsTestName", "association",
-        ItsAssociationPolicy.OPTIONAL)).andReturn(
-            ItsAssociationPolicy.MANDATORY).atLeastOnce();
+
+    expect(itsConfig.getItsAssociationPolicy())
+        .andReturn(ItsAssociationPolicy.MANDATORY).atLeastOnce();
     expect(commit.getFullMessage()).andReturn("bug#4711, bug#42")
         .atLeastOnce();
     expect(commit.getId()).andReturn(commit).anyTimes();
@@ -294,9 +289,9 @@
     RevCommit commit = createMock(RevCommit.class);
     CommitReceivedEvent event = new CommitReceivedEvent(command, project, null,
         commit, null);
-    expect(serverConfig.getEnum("commentLink", "ItsTestName", "association",
-        ItsAssociationPolicy.OPTIONAL)).andReturn(
-            ItsAssociationPolicy.SUGGESTED).atLeastOnce();
+
+    expect(itsConfig.getItsAssociationPolicy())
+        .andReturn(ItsAssociationPolicy.SUGGESTED).atLeastOnce();
     expect(commit.getFullMessage()).andReturn("bug#4711, bug#42")
         .atLeastOnce();
     expect(commit.getId()).andReturn(commit).anyTimes();
@@ -327,9 +322,9 @@
     RevCommit commit = createMock(RevCommit.class);
     CommitReceivedEvent event = new CommitReceivedEvent(command, project, null,
         commit, null);
-    expect(serverConfig.getEnum("commentLink", "ItsTestName", "association",
-        ItsAssociationPolicy.OPTIONAL)).andReturn(
-            ItsAssociationPolicy.MANDATORY).atLeastOnce();
+
+    expect(itsConfig.getItsAssociationPolicy())
+        .andReturn(ItsAssociationPolicy.MANDATORY).atLeastOnce();
     expect(commit.getFullMessage()).andReturn("bug#4711, bug#42")
         .atLeastOnce();
     expect(commit.getId()).andReturn(commit).anyTimes();
@@ -358,9 +353,9 @@
     RevCommit commit = createMock(RevCommit.class);
     CommitReceivedEvent event = new CommitReceivedEvent(command, project, null,
         commit, null);
-    expect(serverConfig.getEnum("commentLink", "ItsTestName", "association",
-        ItsAssociationPolicy.OPTIONAL)).andReturn(
-            ItsAssociationPolicy.SUGGESTED).atLeastOnce();
+
+    expect(itsConfig.getItsAssociationPolicy())
+        .andReturn(ItsAssociationPolicy.SUGGESTED).atLeastOnce();
     expect(commit.getFullMessage()).andReturn("bug#4711, bug#42")
         .atLeastOnce();
     expect(commit.getId()).andReturn(commit).anyTimes();
@@ -391,9 +386,9 @@
     RevCommit commit = createMock(RevCommit.class);
     CommitReceivedEvent event = new CommitReceivedEvent(command, project, null,
         commit, null);
-    expect(serverConfig.getEnum("commentLink", "ItsTestName", "association",
-        ItsAssociationPolicy.OPTIONAL)).andReturn(
-            ItsAssociationPolicy.MANDATORY).atLeastOnce();
+
+    expect(itsConfig.getItsAssociationPolicy())
+        .andReturn(ItsAssociationPolicy.MANDATORY).atLeastOnce();
     expect(commit.getFullMessage()).andReturn("bug#4711, bug#42")
         .atLeastOnce();
     expect(commit.getId()).andReturn(commit).anyTimes();
@@ -422,9 +417,9 @@
     RevCommit commit = createMock(RevCommit.class);
     CommitReceivedEvent event = new CommitReceivedEvent(command, project, null,
         commit, null);
-    expect(serverConfig.getEnum("commentLink", "ItsTestName", "association",
-        ItsAssociationPolicy.OPTIONAL)).andReturn(
-            ItsAssociationPolicy.SUGGESTED).atLeastOnce();
+
+    expect(itsConfig.getItsAssociationPolicy())
+        .andReturn(ItsAssociationPolicy.SUGGESTED).atLeastOnce();
     expect(commit.getFullMessage()).andReturn("bug#4711, bug#42")
         .atLeastOnce();
     expect(commit.getId()).andReturn(commit).anyTimes();
@@ -470,7 +465,9 @@
   }
 
   private void setupCommonMocks() {
-    expect(issueExtractor.getPattern()).andReturn(Pattern.compile("bug#(\\d+)"))
+    expect(itsConfig.getIssuePattern())
+        .andReturn(Pattern.compile("bug#(\\d+)")).anyTimes();
+    expect(itsConfig.isEnabled("myProject", null)).andReturn(true)
         .anyTimes();
   }
 
@@ -489,22 +486,14 @@
       bind(String.class).annotatedWith(PluginName.class)
           .toInstance("ItsTestName");
 
-      serverConfig = createMock(Config.class);
-      bind(Config.class).annotatedWith(GerritServerConfig.class)
-          .toInstance(serverConfig);
-
       issueExtractor = createMock(IssueExtractor.class);
       bind(IssueExtractor.class).toInstance(issueExtractor);
 
       itsFacade = createMock(ItsFacade.class);
       bind(ItsFacade.class).toInstance(itsFacade);
 
-      bind(ItsConfig.class).toInstance(new ItsConfig(null, null, null) {
-        @Override
-        public boolean isEnabled(String project, String branch) {
-          return true;
-        }
-      });
+      itsConfig = createMock(ItsConfig.class);
+      bind(ItsConfig.class).toInstance(itsConfig);
     }
   }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionControllerTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionControllerTest.java
similarity index 90%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionControllerTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionControllerTest.java
index 8819040..e62184a 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionControllerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionControllerTest.java
@@ -11,25 +11,26 @@
 // 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.workflow;
+package com.googlesource.gerrit.plugins.its.base.workflow;
 
+import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.expect;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Set;
-
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.server.events.ChangeEvent;
 import com.google.gerrit.server.events.Event;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 
-import com.googlesource.gerrit.plugins.hooks.its.ItsConfig;
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
-import com.googlesource.gerrit.plugins.hooks.util.PropertyExtractor;
+import com.googlesource.gerrit.plugins.its.base.its.ItsConfig;
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+import com.googlesource.gerrit.plugins.its.base.util.PropertyExtractor;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
 
 public class ActionControllerTest extends LoggingMockingTestCase {
   private Injector injector;
@@ -37,6 +38,7 @@
   private PropertyExtractor propertyExtractor;
   private RuleBase ruleBase;
   private ActionExecutor actionExecutor;
+  private ItsConfig itsConfig;
 
   public void testNoPropertySets() {
     ActionController actionController = createActionController();
@@ -182,9 +184,17 @@
     return injector.getInstance(ActionController.class);
   }
 
+  private void setupCommonMocks() {
+    expect(itsConfig.isEnabled(anyObject(Event.class))).andReturn(true)
+        .anyTimes();
+  }
+
+  @Override
   public void setUp() throws Exception {
     super.setUp();
     injector = Guice.createInjector(new TestModule());
+
+    setupCommonMocks();
   }
 
   private class TestModule extends FactoryModule {
@@ -199,12 +209,8 @@
       actionExecutor = createMock(ActionExecutor.class);
       bind(ActionExecutor.class).toInstance(actionExecutor);
 
-      bind(ItsConfig.class).toInstance(new ItsConfig(null, null, null) {
-        @Override
-        public boolean isEnabled(Event event) {
-          return true;
-        }
-      });
+      itsConfig = createMock(ItsConfig.class);
+      bind(ItsConfig.class).toInstance(itsConfig);
     }
   }
 }
\ No newline at end of file
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionExecutorTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutorTest.java
similarity index 92%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionExecutorTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutorTest.java
index 63293e1..2610b22 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionExecutorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutorTest.java
@@ -11,26 +11,27 @@
 // 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.workflow;
+package com.googlesource.gerrit.plugins.its.base.workflow;
 
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.expectLastCall;
 
+import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+import com.googlesource.gerrit.plugins.its.base.workflow.action.AddComment;
+import com.googlesource.gerrit.plugins.its.base.workflow.action.AddStandardComment;
+import com.googlesource.gerrit.plugins.its.base.workflow.action.AddVelocityComment;
+import com.googlesource.gerrit.plugins.its.base.workflow.action.LogEvent;
+
 import java.io.IOException;
 import java.util.Collections;
 import java.util.Set;
 
-import com.google.common.collect.Sets;
-import com.google.gerrit.server.config.FactoryModule;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
-import com.googlesource.gerrit.plugins.hooks.workflow.action.AddComment;
-import com.googlesource.gerrit.plugins.hooks.workflow.action.AddStandardComment;
-import com.googlesource.gerrit.plugins.hooks.workflow.action.AddVelocityComment;
-import com.googlesource.gerrit.plugins.hooks.workflow.action.LogEvent;
-
 public class ActionExecutorTest extends LoggingMockingTestCase {
   private Injector injector;
 
@@ -199,6 +200,7 @@
     return injector.getInstance(ActionExecutor.class);
   }
 
+  @Override
   public void setUp() throws Exception {
     super.setUp();
     injector = Guice.createInjector(new TestModule());
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionRequestTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionRequestTest.java
similarity index 78%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionRequestTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionRequestTest.java
index f7b160d..d2461dc 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionRequestTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionRequestTest.java
@@ -11,20 +11,20 @@
 // 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.workflow;
+package com.googlesource.gerrit.plugins.its.base.workflow;
 
-import java.io.IOException;
-import java.util.Arrays;
-
-import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
+
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+
+import java.util.Arrays;
 
 public class ActionRequestTest extends LoggingMockingTestCase {
   private Injector injector;
 
-  public void testUnparsedParameterless() throws IOException {
+  public void testUnparsedParameterless() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action");
@@ -32,7 +32,7 @@
         actionRequest.getUnparsed());
   }
 
-  public void testUnparsedNull() throws IOException {
+  public void testUnparsedNull() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest(null);
@@ -40,7 +40,7 @@
         actionRequest.getUnparsed());
   }
 
-  public void testUnparsedSingleParameter() throws IOException {
+  public void testUnparsedSingleParameter() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action param");
@@ -48,7 +48,7 @@
         actionRequest.getUnparsed());
   }
 
-  public void testUnparsedMultipleParameters() throws IOException {
+  public void testUnparsedMultipleParameters() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action param1 param2");
@@ -56,7 +56,7 @@
         actionRequest.getUnparsed());
   }
 
-  public void testNameParameterless() throws IOException {
+  public void testNameParameterless() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action");
@@ -64,7 +64,7 @@
         actionRequest.getName());
   }
 
-  public void testNameSingleParameter() throws IOException {
+  public void testNameSingleParameter() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action param");
@@ -72,7 +72,7 @@
         actionRequest.getName());
   }
 
-  public void testNameMultipleParameters() throws IOException {
+  public void testNameMultipleParameters() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action param1 param2");
@@ -80,7 +80,7 @@
         actionRequest.getName());
   }
 
-  public void testNameNull() throws IOException {
+  public void testNameNull() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest(null);
@@ -88,7 +88,7 @@
         actionRequest.getName());
   }
 
-  public void testParameter1Parameterless() throws IOException {
+  public void testParameter1Parameterless() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action");
@@ -96,7 +96,7 @@
         actionRequest.getParameter(1));
   }
 
-  public void testParameter1Null() throws IOException {
+  public void testParameter1Null() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest(null);
@@ -104,7 +104,7 @@
         actionRequest.getParameter(1));
   }
 
-  public void testParameter1SingleParameter() throws IOException {
+  public void testParameter1SingleParameter() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action param");
@@ -112,7 +112,7 @@
         actionRequest.getParameter(1));
   }
 
-  public void testParemeter1MultipleParameters() throws IOException {
+  public void testParemeter1MultipleParameters() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action param1 param2");
@@ -120,7 +120,7 @@
         actionRequest.getParameter(1));
   }
 
-  public void testParameter3Parameterless() throws IOException {
+  public void testParameter3Parameterless() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action");
@@ -128,7 +128,7 @@
         actionRequest.getParameter(3));
   }
 
-  public void testParameter3Null() throws IOException {
+  public void testParameter3Null() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest(null);
@@ -136,7 +136,7 @@
         actionRequest.getParameter(3));
   }
 
-  public void testParameter3SingleParameter() throws IOException {
+  public void testParameter3SingleParameter() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action param");
@@ -144,7 +144,7 @@
         actionRequest.getParameter(3));
   }
 
-  public void testParemeter3With2Parameters() throws IOException {
+  public void testParemeter3With2Parameters() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action param1 param2");
@@ -152,7 +152,7 @@
         actionRequest.getParameter(3));
   }
 
-  public void testParemeter3With3Parameters() throws IOException {
+  public void testParemeter3With3Parameters() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action param1 param2 " +
@@ -161,7 +161,7 @@
         actionRequest.getParameter(3));
   }
 
-  public void testParemeter3With4Parameters() throws IOException {
+  public void testParemeter3With4Parameters() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action param1 param2 " +
@@ -170,7 +170,7 @@
         actionRequest.getParameter(3));
   }
 
-  public void testParametersParameterless() throws IOException {
+  public void testParametersParameterless() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action");
@@ -180,7 +180,7 @@
         Arrays.asList(actionRequest.getParameters()));
   }
 
-  public void testParametersNull() throws IOException {
+  public void testParametersNull() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest(null);
@@ -190,7 +190,7 @@
         Arrays.asList(actionRequest.getParameters()));
   }
 
-  public void testParametersSingleParameter() throws IOException {
+  public void testParametersSingleParameter() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action param");
@@ -200,7 +200,7 @@
         Arrays.asList(actionRequest.getParameters()));
   }
 
-  public void testParameters3Parameter() throws IOException {
+  public void testParameters3Parameter() {
     replayMocks();
 
     ActionRequest actionRequest = createActionRequest("action param1 param2 " +
@@ -217,6 +217,7 @@
     return factory.create(specification);
   }
 
+  @Override
   public void setUp() throws Exception {
     super.setUp();
     injector = Guice.createInjector(new TestModule());
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ConditionTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ConditionTest.java
similarity index 97%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ConditionTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ConditionTest.java
index 886ab40..f6cd47f 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ConditionTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ConditionTest.java
@@ -11,18 +11,19 @@
 // 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.workflow;
+package com.googlesource.gerrit.plugins.its.base.workflow;
 
 import static org.easymock.EasyMock.expect;
 
-import java.util.Collection;
-import java.util.Collections;
-
 import com.google.common.collect.Lists;
-import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
+
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+
+import java.util.Collection;
+import java.util.Collections;
 
 public class ConditionTest  extends LoggingMockingTestCase {
   private Injector injector;
@@ -317,6 +318,7 @@
     return factory.create(key, value);
   }
 
+  @Override
   public void setUp() throws Exception {
     super.setUp();
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/PropertyTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/PropertyTest.java
similarity index 94%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/PropertyTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/PropertyTest.java
index c977208..2d34838 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/PropertyTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/PropertyTest.java
@@ -11,12 +11,13 @@
 // 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.workflow;
+package com.googlesource.gerrit.plugins.its.base.workflow;
 
-import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
+
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
 
 public class PropertyTest  extends LoggingMockingTestCase {
   private Injector injector;
@@ -112,6 +113,7 @@
     return factory.create(key, value);
   }
 
+  @Override
   public void setUp() throws Exception {
     super.setUp();
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBaseTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleBaseTest.java
similarity index 92%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBaseTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleBaseTest.java
index ffadc1d..596afc7 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBaseTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleBaseTest.java
@@ -11,34 +11,37 @@
 // 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.workflow;
+package com.googlesource.gerrit.plugins.its.base.workflow;
 
 import static org.easymock.EasyMock.expect;
 
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.server.config.SitePath;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+
+import org.eclipse.jgit.util.FileUtils;
+
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.UUID;
 
-import org.eclipse.jgit.util.FileUtils;
-
-import com.google.common.collect.Lists;
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.server.config.FactoryModule;
-import com.google.gerrit.server.config.SitePath;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
-
 public class RuleBaseTest extends LoggingMockingTestCase {
   private Injector injector;
 
-  private File sitePath;
+  private Path sitePath;
   private Rule.Factory ruleFactory;
   private Condition.Factory conditionFactory;
   private ActionRequest.Factory actionRequestFactory;
@@ -366,39 +369,40 @@
       default:
         fail("Unknown ruleBaseKind");
     }
-    File ruleBaseFile = new File(sitePath, "etc" + File.separatorChar + "its" +
-        File.separator + baseName + ".config");
+    File ruleBaseFile = new File(sitePath.toFile(),
+        "etc" + File.separatorChar + "its" + File.separator +
+        baseName + ".config");
 
     File ruleBaseParentFile = ruleBaseFile.getParentFile();
     if (!ruleBaseParentFile.exists()) {
       assertTrue("Failed to create parent (" + ruleBaseParentFile + ") for " +
           "rule base", ruleBaseParentFile.mkdirs());
     }
-    FileWriter unbufferedWriter = new FileWriter(ruleBaseFile);
-    BufferedWriter writer = new BufferedWriter(unbufferedWriter);
-    writer.write(rules);
-    writer.close();
-    unbufferedWriter.close();
+    try (FileWriter unbufferedWriter = new FileWriter(ruleBaseFile);
+        BufferedWriter writer = new BufferedWriter(unbufferedWriter)) {
+      writer.write(rules);
+    }
   }
 
+  @Override
   public void setUp() throws Exception {
     super.setUp();
     cleanupSitePath = false;
     injector = Guice.createInjector(new TestModule());
   }
 
+  @Override
   public void tearDown() throws Exception {
     if (cleanupSitePath) {
-      if (sitePath.exists()) {
-        FileUtils.delete(sitePath, FileUtils.RECURSIVE);
+      if (Files.exists(sitePath)) {
+        FileUtils.delete(sitePath.toFile(), FileUtils.RECURSIVE);
       }
     }
     super.tearDown();
   }
 
-  private File randomTargetFile() {
-    final File t = new File("target");
-    return new File(t, "random-name-" + UUID.randomUUID().toString());
+  private Path randomTargetPath() {
+    return Paths.get("target", "random-name-" + UUID.randomUUID().toString());
   }
 
   private class TestModule extends FactoryModule {
@@ -408,12 +412,12 @@
       bind(String.class).annotatedWith(PluginName.class)
           .toInstance("ItsTestName");
 
-      sitePath = randomTargetFile();
+      sitePath = randomTargetPath();
       assertFalse("sitePath already (" + sitePath + ") already exists",
-          sitePath.exists());
+          Files.exists(sitePath));
       cleanupSitePath = true;
 
-      bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
+      bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath);
 
       ruleFactory = createMock(Rule.Factory.class);
       bind(Rule.Factory.class).toInstance(ruleFactory);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleTest.java
similarity index 95%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleTest.java
index d09a790..4b31618 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleTest.java
@@ -11,20 +11,21 @@
 // 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.workflow;
+package com.googlesource.gerrit.plugins.its.base.workflow;
 
 import static org.easymock.EasyMock.expect;
 
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
-import com.google.common.collect.Lists;
-import com.google.gerrit.server.config.FactoryModule;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
-
 public class RuleTest extends LoggingMockingTestCase {
   private Injector injector;
 
@@ -120,6 +121,7 @@
     return factory.create(name);
   }
 
+  @Override
   public void setUp() throws Exception {
     super.setUp();
     injector = Guice.createInjector(new TestModule());
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddCommentTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddCommentTest.java
similarity index 82%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddCommentTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddCommentTest.java
index d833c55..a86798e 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddCommentTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddCommentTest.java
@@ -11,20 +11,21 @@
 // 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.workflow.action;
+package com.googlesource.gerrit.plugins.its.base.workflow.action;
 
 import static org.easymock.EasyMock.expect;
 
-import java.io.IOException;
-import java.util.HashSet;
-
-import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
-import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
-import com.googlesource.gerrit.plugins.hooks.workflow.ActionRequest;
-import com.googlesource.gerrit.plugins.hooks.workflow.Property;
+
+import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+import com.googlesource.gerrit.plugins.its.base.workflow.ActionRequest;
+import com.googlesource.gerrit.plugins.its.base.workflow.Property;
+
+import java.io.IOException;
+import java.util.HashSet;
 
 public class AddCommentTest extends LoggingMockingTestCase {
   private Injector injector;
@@ -59,6 +60,7 @@
     return injector.getInstance(AddComment.class);
   }
 
+  @Override
   public void setUp() throws Exception {
     super.setUp();
     injector = Guice.createInjector(new TestModule());
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddStandardCommentTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddStandardCommentTest.java
similarity index 96%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddStandardCommentTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddStandardCommentTest.java
index 6b40093..af013b8 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddStandardCommentTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddStandardCommentTest.java
@@ -11,22 +11,22 @@
 // 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.workflow.action;
+package com.googlesource.gerrit.plugins.its.base.workflow.action;
 
 import static org.easymock.EasyMock.expect;
 
-import java.io.IOException;
-import java.util.Set;
-
-
 import com.google.common.collect.Sets;
-import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
-import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
-import com.googlesource.gerrit.plugins.hooks.workflow.ActionRequest;
-import com.googlesource.gerrit.plugins.hooks.workflow.Property;
+
+import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+import com.googlesource.gerrit.plugins.its.base.workflow.ActionRequest;
+import com.googlesource.gerrit.plugins.its.base.workflow.Property;
+
+import java.io.IOException;
+import java.util.Set;
 
 public class AddStandardCommentTest extends LoggingMockingTestCase {
   private Injector injector;
@@ -302,6 +302,7 @@
     action.execute("176", actionRequest, properties);
   }
 
+  @Override
   public void setUp() throws Exception {
     super.setUp();
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddVelocityCommentTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddVelocityCommentTest.java
similarity index 88%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddVelocityCommentTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddVelocityCommentTest.java
index a7de371..5a33db6 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddVelocityCommentTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/action/AddVelocityCommentTest.java
@@ -11,22 +11,24 @@
 // 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.workflow.action;
+package com.googlesource.gerrit.plugins.its.base.workflow.action;
 
 import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.capture;
 import static org.easymock.EasyMock.eq;
 import static org.easymock.EasyMock.expect;
 
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.Writer;
-import java.lang.reflect.InvocationTargetException;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.UUID;
+import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.server.config.SitePath;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+import com.googlesource.gerrit.plugins.its.base.workflow.ActionRequest;
+import com.googlesource.gerrit.plugins.its.base.workflow.Property;
+import com.googlesource.gerrit.plugins.its.base.workflow.action.AddVelocityComment.VelocityAdapterItsFacade;
 
 import org.apache.velocity.VelocityContext;
 import org.apache.velocity.runtime.RuntimeInstance;
@@ -35,21 +37,22 @@
 import org.easymock.IAnswer;
 import org.eclipse.jgit.util.FileUtils;
 
-import com.google.common.collect.Sets;
-import com.google.gerrit.server.config.FactoryModule;
-import com.google.gerrit.server.config.SitePath;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
-import com.googlesource.gerrit.plugins.hooks.workflow.ActionRequest;
-import com.googlesource.gerrit.plugins.hooks.workflow.Property;
-import com.googlesource.gerrit.plugins.hooks.workflow.action.AddVelocityComment.VelocityAdapterItsFacade;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
 
 public class AddVelocityCommentTest extends LoggingMockingTestCase {
   private Injector injector;
 
-  private File sitePath;
+  private Path sitePath;
   private ItsFacade its;
   private RuntimeInstance velocityRuntime;
 
@@ -120,7 +123,7 @@
     properties.add(propertySubject);
 
     IAnswer<Boolean> answer = new VelocityWriterFiller("Rosebud");
-    Capture<VelocityContext> contextCapture = new Capture<VelocityContext>();
+    Capture<VelocityContext> contextCapture = createCapture();
     expect(velocityRuntime.evaluate(capture(contextCapture),
         (Writer)anyObject(), (String)anyObject(), eq("${subject}")))
         .andAnswer(answer);
@@ -182,7 +185,7 @@
     properties.add(propertyReason);
 
     IAnswer<Boolean> answer = new VelocityWriterFiller("Rosebud Life Rosebud");
-    Capture<VelocityContext> contextCapture = new Capture<VelocityContext>();
+    Capture<VelocityContext> contextCapture = createCapture();
     expect(velocityRuntime.evaluate(capture(contextCapture),
         (Writer)anyObject(), (String)anyObject(),
         eq("${subject} ${reason} ${subject}"))).andAnswer(answer);
@@ -202,15 +205,14 @@
   }
 
   public void testItsWrapperFormatLink1Parameter() throws IOException,
-      SecurityException, NoSuchMethodException, IllegalArgumentException,
-      IllegalAccessException, InvocationTargetException {
+      SecurityException, IllegalArgumentException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
     expect(actionRequest.getParameter(1)).andReturn("inline");
     expect(actionRequest.getParameters()).andReturn(
         new String[] {"inline", "Simple-Text"});
 
     IAnswer<Boolean> answer = new VelocityWriterFiller("Simple-Text");
-    Capture<VelocityContext> contextCapture = new Capture<VelocityContext>();
+    Capture<VelocityContext> contextCapture = createCapture();
     expect(velocityRuntime.evaluate(capture(contextCapture),
         (Writer)anyObject(), (String)anyObject(), eq("Simple-Text")))
         .andAnswer(answer);
@@ -238,15 +240,14 @@
   }
 
   public void testItsWrapperFormatLink2Parameters() throws IOException,
-      SecurityException, NoSuchMethodException, IllegalArgumentException,
-      IllegalAccessException, InvocationTargetException {
+      SecurityException, IllegalArgumentException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
     expect(actionRequest.getParameter(1)).andReturn("inline");
     expect(actionRequest.getParameters()).andReturn(
         new String[] {"inline", "Simple-Text"});
 
     IAnswer<Boolean> answer = new VelocityWriterFiller("Simple-Text");
-    Capture<VelocityContext> contextCapture = new Capture<VelocityContext>();
+    Capture<VelocityContext> contextCapture = createCapture();
     expect(velocityRuntime.evaluate(capture(contextCapture),
         (Writer)anyObject(), (String)anyObject(), eq("Simple-Text")))
         .andAnswer(answer);
@@ -327,7 +328,7 @@
     IAnswer<Boolean> answer = new VelocityWriterFiller(
         "Test Template with subject: Rosebud.\n" +
         "Life is the reason for Rosebud.");
-    Capture<VelocityContext> contextCapture = new Capture<VelocityContext>();
+    Capture<VelocityContext> contextCapture = createCapture();
     expect(velocityRuntime.evaluate(capture(contextCapture),
         (Writer)anyObject(), (String)anyObject(),
         eq("Test Template with subject: ${subject}.\n" +
@@ -353,48 +354,48 @@
   }
 
   private void injectTestTemplate(String template) throws IOException {
-    File templateParentFile = new File(sitePath, "etc" + File.separatorChar + "its" +
-        File.separator + "templates");
+    File templateParentFile = new File(sitePath.toFile(), "etc" +
+        File.separatorChar + "its" + File.separator + "templates");
     assertTrue("Failed to create parent (" + templateParentFile + ") for " +
         "rule base", templateParentFile.mkdirs());
     File templateFile = new File(templateParentFile, "test-template.vm");
 
-    FileWriter unbufferedWriter = new FileWriter(templateFile);
-    BufferedWriter writer = new BufferedWriter(unbufferedWriter);
-    writer.write(template);
-    writer.close();
-    unbufferedWriter.close();
+    try (FileWriter unbufferedWriter = new FileWriter(templateFile);
+        BufferedWriter writer = new BufferedWriter(unbufferedWriter)) {
+      writer.write(template);
+    }
   }
 
+  @Override
   public void setUp() throws Exception {
     super.setUp();
     cleanupSitePath = false;
     injector = Guice.createInjector(new TestModule());
   }
 
+  @Override
   public void tearDown() throws Exception {
     if (cleanupSitePath) {
-      if (sitePath.exists()) {
-        FileUtils.delete(sitePath, FileUtils.RECURSIVE);
+      if (Files.exists(sitePath)) {
+        FileUtils.delete(sitePath.toFile(), FileUtils.RECURSIVE);
       }
     }
     super.tearDown();
   }
 
-  private File randomTargetFile() {
-    final File t = new File("target");
-    return new File(t, "random-name-" + UUID.randomUUID().toString());
+  private Path randomTargetPath() {
+    return Paths.get("target", "random-name-" + UUID.randomUUID().toString());
   }
 
   private class TestModule extends FactoryModule {
     @Override
     protected void configure() {
-      sitePath = randomTargetFile();
+      sitePath = randomTargetPath();
       assertFalse("sitePath already (" + sitePath + ") already exists",
-          sitePath.exists());
+          Files.exists(sitePath));
       cleanupSitePath = true;
 
-      bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
+      bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath);
 
       its = createMock(ItsFacade.class);
       bind(ItsFacade.class).toInstance(its);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/LogEventTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/action/LogEventTest.java
similarity index 92%
rename from src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/LogEventTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/action/LogEventTest.java
index 7d607b2..3d147a9 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/LogEventTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/action/LogEventTest.java
@@ -11,24 +11,25 @@
 // 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.workflow.action;
+package com.googlesource.gerrit.plugins.its.base.workflow.action;
 
 import static org.easymock.EasyMock.expect;
 
+import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+import com.googlesource.gerrit.plugins.its.base.workflow.ActionRequest;
+import com.googlesource.gerrit.plugins.its.base.workflow.Property;
+
+import org.apache.log4j.Level;
+
 import java.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
 
-import org.apache.log4j.Level;
-
-import com.google.common.collect.Sets;
-import com.google.gerrit.server.config.FactoryModule;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
-import com.googlesource.gerrit.plugins.hooks.workflow.ActionRequest;
-import com.googlesource.gerrit.plugins.hooks.workflow.Property;
-
 public class LogEventTest extends LoggingMockingTestCase {
   private Injector injector;
 
@@ -135,6 +136,7 @@
     return injector.getInstance(LogEvent.class);
   }
 
+  @Override
   public void setUp() throws Exception {
     super.setUp();
     injector = Guice.createInjector(new TestModule());
@@ -154,6 +156,7 @@
       this.toString = toString;
     }
 
+    @Override
     public String toString() {
       return toString;
     }