Structured configuration classes

To achieve layered config fragments, Properties class structure is
drastically changed.

In addition, a lot of string values e.g. AMQP header field name, default
value, is associated with field in Section class using annotation.

Maybe my big patch would be finished. Remaining logging, documentation,
etc.
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/Keys.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/Keys.java
deleted file mode 100644
index 14de98a..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/Keys.java
+++ /dev/null
@@ -1,47 +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.rabbitmq;
-
-public enum Keys {
-  AMQP_URI("amqp.uri", null, "amqp://localhost"),
-  AMQP_USERNAME("amqp.username", null, "guest"),
-  AMQP_PASSWORD("amqp.password", null, "guest"),
-  EXCHANGE_NAME("exchange.name", null, "gerrit.publish"),
-  MESSAGE_DELIVERY_MODE("message.deliveryMode", null, 1),
-  MESSAGE_PRIORITY("message.priority", null, 0),
-  MESSAGE_ROUTINGKEY("message.routingKey", null, ""),
-  GERRIT_NAME("gerrit.name", "gerrit-name", ""),
-  GERRIT_HOSTNAME("gerrit.hostname", "gerrit-host", ""),
-  GERRIT_SCHEME("gerrit.scheme", "gerrit-scheme", "ssh"),
-  GERRIT_PORT("gerrit.port", "gerrit-port", 29418),
-  GERRIT_FRONT_URL("gerrit.canonicalWebUrl", "gerrit-front-url", ""),
-  GERRIT_VERSION("gerrit.version", "gerrit-version", null),
-  GERRIT_LISTENAS("gerrit.listenAs", null, null),
-  MONITOR_INTERVAL("monitor.interval", null, 15000),
-  MONITOR_FAILURECOUNT("monitor.failureCount", null, 15);
-
-  public String section;
-  public String name;
-  public String key;
-  public Object defaultVal;
-
-  Keys(String property, String key, Object defaultVal) {
-    String[] part = property.split("\\.");
-    this.section = part[0];
-    this.name = part[1];
-    this.key = key;
-    this.defaultVal = defaultVal;
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/Module.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/Module.java
index e68f53b..a2bc44f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/Module.java
@@ -19,7 +19,17 @@
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.inject.AbstractModule;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
+import com.google.inject.name.Names;
 
+import com.googlesource.gerrit.plugins.rabbitmq.config.PluginProperties;
+import com.googlesource.gerrit.plugins.rabbitmq.config.Properties;
+import com.googlesource.gerrit.plugins.rabbitmq.config.PropertiesFactory;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.AMQP;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Exchange;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Gerrit;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Message;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Monitor;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Section;
 import com.googlesource.gerrit.plugins.rabbitmq.message.DefaultChangeListener;
 import com.googlesource.gerrit.plugins.rabbitmq.message.IdentifiedChangeListener;
 import com.googlesource.gerrit.plugins.rabbitmq.message.MessagePublisher;
@@ -36,14 +46,21 @@
 
   @Override
   protected void configure() {
-    bind(PropertiesStore.class);
+    bind(Gerrit.class);
     bind(BCSolver.class);
     bind(IdentifiedChangeListener.class);
     bind(RabbitMQManager.class);
 
+    bind(Section.class).annotatedWith(Names.named("amqp")).to(AMQP.class);
+    bind(Section.class).annotatedWith(Names.named("exchange")).to(Exchange.class);
+    bind(Section.class).annotatedWith(Names.named("gerrit")).to(Gerrit.class);
+    bind(Section.class).annotatedWith(Names.named("message")).to(Message.class);
+    bind(Section.class).annotatedWith(Names.named("monitor")).to(Monitor.class);
+
     install(new FactoryModuleBuilder().implement(Solver.class, BCSolver.class).build(SolverFactory.class));
     install(new FactoryModuleBuilder().implement(Session.class, AMQPSession.class).build(SessionFactory.class));
     install(new FactoryModuleBuilder().implement(Publisher.class, MessagePublisher.class).build(PublisherFactory.class));
+    install(new FactoryModuleBuilder().implement(Properties.class, PluginProperties.class).build(PropertiesFactory.class));
 
     DynamicSet.bind(binder(), LifecycleListener.class).to(RabbitMQManager.class);
     DynamicSet.bind(binder(), LifecycleListener.class).to(DefaultChangeListener.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/PropertiesStore.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/PropertiesStore.java
deleted file mode 100644
index ff77481..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/PropertiesStore.java
+++ /dev/null
@@ -1,101 +0,0 @@
-// 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.rabbitmq;
-
-import com.google.gerrit.extensions.annotations.PluginData;
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-import com.googlesource.gerrit.plugins.rabbitmq.config.Properties;
-import com.googlesource.gerrit.plugins.rabbitmq.config.internal.GerritFrontUrl;
-
-import org.eclipse.jgit.lib.Config;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.AbstractList;
-import java.util.ArrayList;
-import java.util.List;
-
-@Singleton
-public class PropertiesStore extends AbstractList<Properties> {
-
-  public static final String FILE_EXT = ".config";
-  public static final String SITE_DIR = "site";
-
-  private static final Logger LOGGER = LoggerFactory.getLogger(PropertiesStore.class);
-
-
-  private final List<Properties> propertiesStore;
-  private final String pluginName;
-  private final Path pluginDataDir;
-  private final Config serverConfig;
-
-  @Inject
-  public PropertiesStore(
-      @PluginName final String pluginName,
-      @PluginData final File pluginData,
-      @GerritServerConfig final Config serverConfig) {
-    this.propertiesStore = new ArrayList<>();
-    this.pluginName = pluginName;
-    this.pluginDataDir = pluginData.toPath();
-    this.serverConfig = serverConfig;
-  }
-
-  public void load() {
-    // Load base
-    Properties base = new Properties(pluginDataDir.resolve(pluginName + FILE_EXT));
-    base.load();
-    ((GerritFrontUrl)base).setGerritFrontUrlFromConfig(serverConfig);
-
-    // Load site
-    try (DirectoryStream<Path> ds = Files.newDirectoryStream(pluginDataDir.resolve(SITE_DIR), "*" + FILE_EXT)) {
-      for (Path configFile : ds) {
-        Properties site = new Properties(configFile);
-        if (site.load(base)) {
-          propertiesStore.add(site);
-        }
-      }
-    } catch (IOException iex) {
-      LOGGER.warn(iex.getMessage());
-    }
-  }
-
-  @Override
-  public Properties get(int index) {
-    return propertiesStore.get(index);
-  }
-
-  public Properties get(String name) {
-    for (Properties p : propertiesStore) {
-      if (p.getName().equals(name)) {
-        return p;
-      }
-    }
-    return null;
-  }
-
-  @Override
-  public int size() {
-    return propertiesStore.size();
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/RabbitMQManager.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/RabbitMQManager.java
index 95aaf60..043d529 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/RabbitMQManager.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/RabbitMQManager.java
@@ -14,25 +14,32 @@
 
 package com.googlesource.gerrit.plugins.rabbitmq;
 
+import com.google.gerrit.extensions.annotations.PluginData;
+import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
 import com.googlesource.gerrit.plugins.rabbitmq.config.Properties;
+import com.googlesource.gerrit.plugins.rabbitmq.config.PropertiesFactory;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Gerrit;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Section;
 import com.googlesource.gerrit.plugins.rabbitmq.message.DefaultChangeListener;
 import com.googlesource.gerrit.plugins.rabbitmq.message.IdentifiedChangeListener;
-import com.googlesource.gerrit.plugins.rabbitmq.message.MessagePublisher;
 import com.googlesource.gerrit.plugins.rabbitmq.message.Publisher;
 import com.googlesource.gerrit.plugins.rabbitmq.message.PublisherFactory;
-import com.googlesource.gerrit.plugins.rabbitmq.session.Session;
-import com.googlesource.gerrit.plugins.rabbitmq.session.SessionFactory;
-import com.googlesource.gerrit.plugins.rabbitmq.solver.BCSolver;
 import com.googlesource.gerrit.plugins.rabbitmq.solver.Solver;
 import com.googlesource.gerrit.plugins.rabbitmq.solver.SolverFactory;
 
+import org.apache.commons.lang.builder.ToStringBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -40,24 +47,34 @@
 public class RabbitMQManager implements LifecycleListener {
 
   private static final Logger LOGGER = LoggerFactory.getLogger(RabbitMQManager.class);
+
+  public static final String FILE_EXT = ".config";
+  public static final String SITE_DIR = "site";
+
+  private final String pluginName;
+  private final Path pluginDataDir;
   private final DefaultChangeListener defaultChangeListener;
   private final IdentifiedChangeListener identifiedChangeListener;
   private final PublisherFactory publisherFactory;
-  private final PropertiesStore propertiesStore;
+  private final PropertiesFactory propFactory;
   private final SolverFactory solverFactory;
   private final List<Publisher> publisherList = new ArrayList<>();
 
   @Inject
   public RabbitMQManager(
-      DefaultChangeListener defaultChangeListener,
-      IdentifiedChangeListener identifiedChangeListener,
-      PublisherFactory publisherFactory,
-      PropertiesStore propertiesStore,
-      SolverFactory solverFactory) {
+      @PluginName final String pluginName,
+      @PluginData final File pluginData,
+      final DefaultChangeListener defaultChangeListener,
+      final IdentifiedChangeListener identifiedChangeListener,
+      final PublisherFactory publisherFactory,
+      final PropertiesFactory propFactory,
+      final SolverFactory solverFactory) {
+    this.pluginName = pluginName;
+    this.pluginDataDir = pluginData.toPath();
     this.defaultChangeListener = defaultChangeListener;
     this.identifiedChangeListener = identifiedChangeListener;
     this.publisherFactory = publisherFactory;
-    this.propertiesStore = propertiesStore;
+    this.propFactory = propFactory;
     this.solverFactory = solverFactory;
   }
 
@@ -66,12 +83,13 @@
     Solver solver = solverFactory.create();
     solver.solve();
 
-    propertiesStore.load();
-    for (Properties properties : propertiesStore) {
+    List<Properties> propList = load();
+    for (Properties properties : propList) {
       Publisher publisher = publisherFactory.create(properties);
       publisher.start();
-      if (properties.hasListenAs()) {
-        identifiedChangeListener.addPublisher(publisher, properties.getListenAs());
+      String listenAs = properties.getSection(Gerrit.class).listenAs;
+      if (!listenAs.isEmpty()) {
+        identifiedChangeListener.addPublisher(publisher, listenAs);
       } else {
         defaultChangeListener.addPublisher(publisher);
       }
@@ -83,7 +101,8 @@
   public void stop() {
     for (Publisher publisher : publisherList) {
       publisher.stop();
-      if (publisher.getSession().getProperties().hasListenAs()) {
+      String listenAs = publisher.getProperties().getSection(Gerrit.class).listenAs;
+      if (!listenAs.isEmpty()) {
         identifiedChangeListener.removePublisher(publisher);
       } else {
         defaultChangeListener.removePublisher(publisher);
@@ -91,4 +110,24 @@
     }
     publisherList.clear();
   }
+
+  private List<Properties> load() {
+    List<Properties> propList = new ArrayList<>();
+    // Load base
+    Properties base = propFactory.create(pluginDataDir.resolve(pluginName + FILE_EXT));
+    base.load();
+
+    // Load site
+    try (DirectoryStream<Path> ds = Files.newDirectoryStream(pluginDataDir.resolve(SITE_DIR), "*" + FILE_EXT)) {
+      for (Path configFile : ds) {
+        Properties site = propFactory.create(configFile);
+        if (site.load(base)) {
+          propList.add(site);
+        }
+      }
+    } catch (IOException iex) {
+      LOGGER.warn(iex.getMessage());
+    }
+    return propList;
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/annotation/Default.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/annotation/Default.java
new file mode 100644
index 0000000..87eacd9
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/annotation/Default.java
@@ -0,0 +1,25 @@
+// 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.rabbitmq.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Default {
+  String value() default "";
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/annotation/Limit.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/annotation/Limit.java
new file mode 100644
index 0000000..4c33219
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/annotation/Limit.java
@@ -0,0 +1,26 @@
+// 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.rabbitmq.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Limit {
+  int max() default -1;
+  int min() default -1;
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/annotation/MessageHeader.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/annotation/MessageHeader.java
new file mode 100644
index 0000000..401ef49
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/annotation/MessageHeader.java
@@ -0,0 +1,25 @@
+// 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.rabbitmq.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface MessageHeader {
+  String value() default "";
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/AMQProperties.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/AMQProperties.java
index 15adc06..eda2f0d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/AMQProperties.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/AMQProperties.java
@@ -14,13 +14,16 @@
 
 package com.googlesource.gerrit.plugins.rabbitmq.config;
 
-import com.googlesource.gerrit.plugins.rabbitmq.Keys;
+import com.googlesource.gerrit.plugins.rabbitmq.annotation.MessageHeader;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Message;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Section;
 import com.rabbitmq.client.AMQP;
 
 import org.apache.commons.codec.CharEncoding;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.lang.reflect.Field;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -31,29 +34,49 @@
 
   private static final Logger LOGGER = LoggerFactory.getLogger(AMQProperties.class);
 
-  private final Properties properties;
+  private final PluginProperties properties;
   private AMQP.BasicProperties amqpProperties;
 
-  public AMQProperties(Properties properties) {
+  public AMQProperties(PluginProperties properties) {
     this.properties = properties;
   }
 
   public AMQP.BasicProperties getBasicProperties() {
     if (amqpProperties == null) {
       Map<String, Object> headers = new HashMap<>();
-      headers.put(Keys.GERRIT_NAME.key, properties.getString(Keys.GERRIT_NAME));
-      headers.put(Keys.GERRIT_HOSTNAME.key, properties.getString(Keys.GERRIT_HOSTNAME));
-      headers.put(Keys.GERRIT_SCHEME.key, properties.getString(Keys.GERRIT_SCHEME));
-      headers.put(Keys.GERRIT_PORT.key, String.valueOf(properties.getInt(Keys.GERRIT_PORT)));
-      headers.put(Keys.GERRIT_FRONT_URL.key, properties.getGerritFrontUrl());
-      headers.put(Keys.GERRIT_VERSION.key, properties.getGerritVersion());
-
+      for (Section section : properties.getSections()) {
+        for (Field f : section.getClass().getFields()) {
+          if (f.isAnnotationPresent(MessageHeader.class)) {
+            MessageHeader mh = f.getAnnotation(MessageHeader.class);
+            try {
+              switch(f.getType().getSimpleName()) {
+                case "String":
+                  headers.put(mh.value(), f.get(section).toString());
+                  break;
+                case "Integer":
+                  headers.put(mh.value(), f.getInt(section));
+                  break;
+                case "Long":
+                  headers.put(mh.value(), f.getLong(section));
+                  break;
+                case "Boolean":
+                  headers.put(mh.value(), f.getBoolean(section));
+                  break;
+                default:
+                  break;
+              }
+            } catch (Exception ex) {
+            }
+          }
+        }
+      }
+      Message message = properties.getSection(Message.class);
       AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
       builder.appId(EVENT_APPID);
       builder.contentEncoding(CharEncoding.UTF_8);
       builder.contentType(CONTENT_TYPE_JSON);
-      builder.deliveryMode(properties.getInt(Keys.MESSAGE_DELIVERY_MODE));
-      builder.priority(properties.getInt(Keys.MESSAGE_PRIORITY));
+      builder.deliveryMode(message.deliveryMode);
+      builder.priority(message.priority);
       builder.headers(headers);
 
       amqpProperties = builder.build();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/PluginProperties.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/PluginProperties.java
new file mode 100644
index 0000000..798912e
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/PluginProperties.java
@@ -0,0 +1,203 @@
+// 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.rabbitmq.config;
+
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+import com.google.inject.name.Named;
+
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Gerrit;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Monitor;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Section;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Sections;
+
+import org.apache.commons.io.FilenameUtils;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class PluginProperties implements Properties {
+
+  private static final Logger LOGGER = LoggerFactory.getLogger(PluginProperties.class);
+
+  private final static int MINIMUM_CONNECTION_MONITOR_INTERVAL = 5000;
+
+  private final List<Section> sections = new ArrayList<>();
+  private final Path propertiesFile;
+  private AMQProperties amqProperties;
+
+  @AssistedInject
+  public PluginProperties(
+      @Named("amqp") final Section amqp,
+      @Named("exchange") final Section exchange,
+      @Named("gerrit") final Section gerrit,
+      @Named("message") final Section message,
+      @Named("monitor") final Section monitor) {
+    this(amqp, exchange, gerrit, message, monitor, null);
+  }
+
+  @AssistedInject
+  public PluginProperties(
+      @Named("amqp") final Section amqp,
+      @Named("exchange") final Section exchange,
+      @Named("gerrit") final Section gerrit,
+      @Named("message") final Section message,
+      @Named("monitor") final Section monitor,
+      @Assisted Path propertiesFile) {
+    this.sections.addAll(Arrays.asList(
+        amqp,
+        exchange,
+        gerrit,
+        message,
+        monitor
+    ));
+    this.propertiesFile = propertiesFile;
+  }
+
+  private void initialize() {
+    for (Section section : sections) {
+      Sections.initialize(section);
+    }
+  }
+
+  @Override
+  public Config toConfig() {
+    Config config = new Config();
+    for (Section section : sections) {
+      config = Sections.toConfig(section, config);
+    }
+    return config;
+  }
+
+  @Override
+  public boolean load() {
+    return load(null);
+  }
+
+  @Override
+  public boolean load(Properties baseProperties) {
+    initialize();
+    LOGGER.info("Loading {} ...", propertiesFile);
+    if (!Files.exists(propertiesFile)) {
+      LOGGER.warn("No {}", propertiesFile);
+      return false;
+    }
+
+    FileBasedConfig cfg = new FileBasedConfig(propertiesFile.toFile(), FS.DETECTED);
+    try {
+      cfg.load();
+    } catch (ConfigInvalidException e) {
+      LOGGER.info("{} has invalid format: {}", propertiesFile, e.getMessage());
+      return false;
+    } catch (IOException e) {
+      LOGGER.info("Cannot read {}: {}", propertiesFile, e.getMessage());
+      return false;
+    }
+    for (Section section : getSections()) {
+      if (baseProperties != null) {
+        Sections.fromConfig(section, baseProperties.toConfig(), cfg);
+      } else {
+        Sections.fromConfig(section, cfg);
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public Path getPath() {
+    return propertiesFile;
+  }
+
+  @Override
+  public String getName() {
+    if (propertiesFile != null) {
+      return FilenameUtils.removeExtension(propertiesFile.getFileName().toString());
+    }
+    return null;
+  }
+
+  @Override
+  public List<Section> getSections() {
+    return Collections.unmodifiableList(sections);
+  }
+
+  @Override
+  public <T extends Section> T getSection(Class<T> clazz) {
+    for (Section section : sections) {
+      if (section.getClass() == clazz) {
+        return clazz.cast(section);
+      }
+    }
+    return null;
+  }
+
+  public String getGerritFrontUrl() {
+    Gerrit gerrit = (Gerrit) getSection(Gerrit.class);
+    if (gerrit != null) {
+      return gerrit.canonicalWebUrl;
+    }
+    return null;
+  }
+
+  public boolean hasListenAs() {
+    Gerrit gerrit = (Gerrit) getSection(Gerrit.class);
+    if (gerrit != null) {
+      return gerrit.listenAs.isEmpty();
+    }
+    return false;
+  }
+
+  public String getListenAs() {
+    Gerrit gerrit = (Gerrit) getSection(Gerrit.class);
+    if (gerrit != null) {
+      return gerrit.listenAs;
+    }
+    return null;
+  }
+
+  public String getGerritVersion() {
+    Gerrit gerrit = (Gerrit) getSection(Gerrit.class);
+    if (gerrit != null) {
+      return gerrit.version;
+    }
+    return null;
+  }
+
+  public int getConnectionMonitorInterval() {
+    Monitor monitor = (Monitor) getSection(Monitor.class);
+    if (monitor != null && monitor.interval < MINIMUM_CONNECTION_MONITOR_INTERVAL) {
+      return monitor.interval;
+    }
+    return MINIMUM_CONNECTION_MONITOR_INTERVAL;
+  }
+
+  public AMQProperties getAMQProperties() {
+    if (amqProperties == null) {
+      amqProperties = new AMQProperties(this);
+    }
+    return amqProperties;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/Properties.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/Properties.java
index 1e88689..a51ad90 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/Properties.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/Properties.java
@@ -1,149 +1,19 @@
-// 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.rabbitmq.config;
 
-import com.google.gerrit.common.Version;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Section;
 
-import com.googlesource.gerrit.plugins.rabbitmq.Keys;
-import com.googlesource.gerrit.plugins.rabbitmq.config.internal.GerritFrontUrl;
-
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.lang.StringUtils;
-import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-import java.io.IOException;
-import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.List;
 
-public class Properties implements GerritFrontUrl {
-
-  private static final Logger LOGGER = LoggerFactory.getLogger(Properties.class);
-
-  private final static int MINIMUM_CONNECTION_MONITOR_INTERVAL = 5000;
-
-  private final Path propertiesFile;
-  private Config pluginConfig;
-  private AMQProperties amqProperties;
-
-  public Properties(final Path propertiesFile) {
-    this.propertiesFile = propertiesFile;
-  }
-
-  public boolean load() {
-    return load(null);
-  }
-
-  public boolean load(final Properties baseProperties) {
-    pluginConfig = new Config();
-    LOGGER.info("Loading {} ...", propertiesFile);
-    if (!Files.exists(propertiesFile)) {
-      LOGGER.warn("No {}", propertiesFile);
-      return false;
-    }
-
-    FileBasedConfig cfg;
-    try {
-      if (baseProperties != null) {
-        cfg = new FileBasedConfig(baseProperties.getConfig(), propertiesFile.toFile(), FS.DETECTED);
-      } else {
-        cfg = new FileBasedConfig(propertiesFile.toFile(), FS.DETECTED);
-      }
-      cfg.load();
-    } catch (ConfigInvalidException e) {
-      LOGGER.info("{} has invalid format: {}", propertiesFile, e.getMessage());
-      return false;
-    } catch (IOException e) {
-      LOGGER.info("Cannot read {}: {}", propertiesFile, e.getMessage());
-      return false;
-    }
-    pluginConfig = cfg;
-    return true;
-  }
-
-  public Config getConfig() {
-    return pluginConfig;
-  }
-
-  public Path getPath() {
-    return propertiesFile;
-  }
-
-  public String getName() {
-    return FilenameUtils.removeExtension(propertiesFile.getFileName().toString());
-  }
-
-  public String getString(Keys key) {
-    String val = pluginConfig.getString(key.section, null, key.name);
-    if (val == null) {
-      return key.defaultVal.toString();
-    }
-    return val;
-  }
-
-  public int getInt(Keys key) {
-    return pluginConfig.getInt(key.section, key.name, new Integer(key.defaultVal.toString()));
-  }
-
-  public boolean getBoolean(Keys key) {
-    return pluginConfig.getBoolean(key.section, key.name, new Boolean(key.defaultVal.toString()));
-  }
-
-  public String getGerritFrontUrl() {
-    return StringUtils.stripToEmpty(pluginConfig.getString(
-        Keys.GERRIT_FRONT_URL.section, null, Keys.GERRIT_FRONT_URL.name));
-  }
-
-  public boolean hasListenAs() {
-    return !getListenAs().isEmpty();
-  }
-
-  public String getListenAs() {
-    return StringUtils.stripToEmpty(pluginConfig.getString(
-        Keys.GERRIT_LISTENAS.section, null, Keys.GERRIT_LISTENAS.name));
-  }
-
-  public String getGerritVersion() {
-    return StringUtils.stripToEmpty(Version.getVersion());
-  }
-
-  public int getConnectionMonitorInterval() {
-    int interval = getInt(Keys.MONITOR_INTERVAL);
-    if (interval < MINIMUM_CONNECTION_MONITOR_INTERVAL) {
-      return MINIMUM_CONNECTION_MONITOR_INTERVAL;
-    }
-    return interval;
-  }
-
-  public AMQProperties getAMQProperties() {
-    if (amqProperties == null) {
-      amqProperties = new AMQProperties(this);
-    }
-    return amqProperties;
-  }
-
-  @Override
-  public void setGerritFrontUrlFromConfig(Config config) {
-    if (pluginConfig != null) {
-      pluginConfig.setString(
-        Keys.GERRIT_FRONT_URL.section, null, Keys.GERRIT_FRONT_URL.name,
-        config.getString(Keys.GERRIT_FRONT_URL.section, null, Keys.GERRIT_FRONT_URL.name));
-    }
-  }
+public interface Properties extends Cloneable {
+  public Config toConfig();
+  public boolean load();
+  public boolean load(Properties baseProperties);
+  public Path getPath();
+  public String getName();
+  public List<Section> getSections();
+  public <T extends Section> T getSection(Class<T> clazz);
+  public AMQProperties getAMQProperties();
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/PropertiesFactory.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/PropertiesFactory.java
new file mode 100644
index 0000000..a82ebac
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/PropertiesFactory.java
@@ -0,0 +1,22 @@
+// 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.rabbitmq.config;
+
+import java.nio.file.Path;
+
+public interface PropertiesFactory {
+  Properties create();
+  Properties create(Path propertiesFile);
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/AMQP.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/AMQP.java
new file mode 100644
index 0000000..11446cc
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/AMQP.java
@@ -0,0 +1,29 @@
+// 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.rabbitmq.config.section;
+
+import com.googlesource.gerrit.plugins.rabbitmq.annotation.Default;
+
+public class AMQP implements Section {
+
+  @Default("amqp://localhost")
+  public String uri;
+
+  @Default("guest")
+  public String username;
+
+  @Default("guest")
+  public String password;
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Exchange.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Exchange.java
new file mode 100644
index 0000000..350a206
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Exchange.java
@@ -0,0 +1,23 @@
+// 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.rabbitmq.config.section;
+
+import com.googlesource.gerrit.plugins.rabbitmq.annotation.Default;
+
+public class Exchange implements Section {
+
+  @Default("gerrit.publish")
+  public String name;
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Gerrit.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Gerrit.java
new file mode 100644
index 0000000..5ad4a49
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Gerrit.java
@@ -0,0 +1,59 @@
+// 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.rabbitmq.config.section;
+
+import com.google.gerrit.common.Version;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+
+import com.googlesource.gerrit.plugins.rabbitmq.annotation.Default;
+import com.googlesource.gerrit.plugins.rabbitmq.annotation.MessageHeader;
+
+import org.eclipse.jgit.lib.Config;
+
+public class Gerrit implements Section {
+
+  @Default
+  @MessageHeader("gerrit-name")
+  public String name;
+
+  @Default
+  @MessageHeader("gerrit-host")
+  public String hostname;
+
+  @Default("ssh")
+  @MessageHeader("gerrit-scheme")
+  public String scheme;
+
+  @Default("29418")
+  @MessageHeader("gerrit-port")
+  public Integer port;
+
+  @MessageHeader("gerrit-front-url")
+  public String canonicalWebUrl;
+
+  @MessageHeader("gerrit-version")
+  public String version;
+
+  @Default
+  public String listenAs;
+
+  @Inject
+  public Gerrit(final @GerritServerConfig Config config) {
+    this.canonicalWebUrl = config.getString("gerrit", null, "canonicalWebUrl");
+    this.version = Version.getVersion();
+  }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Message.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Message.java
new file mode 100644
index 0000000..4760c4c
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Message.java
@@ -0,0 +1,29 @@
+// 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.rabbitmq.config.section;
+
+import com.googlesource.gerrit.plugins.rabbitmq.annotation.Default;
+
+public class Message implements Section {
+
+  @Default("1")
+  public Integer deliveryMode;
+
+  @Default("0")
+  public Integer priority;
+
+  @Default
+  public String routingKey;
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Monitor.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Monitor.java
new file mode 100644
index 0000000..6f91648
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Monitor.java
@@ -0,0 +1,28 @@
+// 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.rabbitmq.config.section;
+
+import com.googlesource.gerrit.plugins.rabbitmq.annotation.Default;
+import com.googlesource.gerrit.plugins.rabbitmq.annotation.Limit;
+
+public class Monitor implements Section {
+
+  @Default("15000")
+  @Limit(min=5000)
+  public Integer interval;
+
+  @Default("15")
+  public Integer failureCount;
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Section.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Section.java
new file mode 100644
index 0000000..dcb0a9a
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Section.java
@@ -0,0 +1,18 @@
+// 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.rabbitmq.config.section;
+
+public interface Section {
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Sections.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Sections.java
new file mode 100644
index 0000000..f5aef6c
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/config/section/Sections.java
@@ -0,0 +1,129 @@
+// 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.rabbitmq.config.section;
+
+import com.googlesource.gerrit.plugins.rabbitmq.annotation.Default;
+import com.googlesource.gerrit.plugins.rabbitmq.annotation.Limit;
+
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Field;
+import java.util.Set;
+
+public final class Sections {
+  private static final Logger LOGGER = LoggerFactory.getLogger(Sections.class);
+
+  public static final <T extends Section> String getName(T section) {
+    return section.getClass().getSimpleName().toLowerCase();
+  }
+
+  public static final <T extends Section> T initialize(T section) {
+    Field[] fs = section.getClass().getFields();
+    for (Field f : fs) {
+      try {
+        if (f.isAnnotationPresent(Default.class)) {
+          Default a = f.getAnnotation(Default.class);
+          Class<?> type = f.getType();
+          if (type == String.class) {
+            f.set(section, new String(a.value()));
+          } else if (type == Integer.class) {
+            f.set(section, new Integer(a.value()));
+          } else if (type == Long.class) {
+            f.set(section, new Long(a.value()));
+          } else if (type == Boolean.class) {
+            f.set(section, new Boolean(a.value()));
+          }
+        }
+      } catch (Exception ex) {
+        LOGGER.warn("Exception during initialize: {}", f.getName());
+      }
+    }
+    return section;
+  }
+
+  public static final <T extends Section> Config toConfig(T section) {
+    return toConfig(section, new Config());
+  }
+
+  public static final <T extends Section> Config toConfig(T section, Config config) {
+    Field[] fs = section.getClass().getFields();
+    for (Field f : fs) {
+      try {
+        Class<?> type = f.getType();
+        Object obj = f.get(section);
+        if (obj != null) {
+          if (type == String.class) {
+            config.setString(getName(section), null, f.getName(), String.class.cast(obj));
+          } else if (type == Integer.class) {
+            config.setInt(getName(section), null, f.getName(), Integer.class.cast(obj));
+          } else if (type == Long.class) {
+            config.setLong(getName(section), null, f.getName(), Long.class.cast(obj));
+          } else if (type == Boolean.class) {
+            config.setBoolean(getName(section), null, f.getName(), Boolean.class.cast(obj));
+          }
+        }
+      } catch (Exception ex) {
+        LOGGER.warn("Exception during toConfig: {}", f.getName());
+        LOGGER.info("{}", ex.getMessage());
+      }
+    }
+    return config;
+  }
+
+  public static final <T extends Section> Section fromConfig(T section, Config... configs) {
+    for (Config config : configs) {
+      if (config != null) {
+        Set<String> names = config.getNames(getName(section));
+        Field[] fs = section.getClass().getFields();
+
+        for (Field f : fs) {
+          try {
+            if (names.contains(f.getName())) {
+              Class<?> type = f.getType();
+              if (type == String.class) {
+                f.set(section, new String(config.getString(getName(section), null, f.getName())));
+              } else if (type == Integer.class) {
+                f.set(section, new Integer(config.getInt(getName(section), null, f.getName(), 0)));
+              } else if (type == Long.class) {
+                f.set(section, new Long(config.getLong(getName(section), null, f.getName(), 0)));
+              } else if (type == Boolean.class) {
+                f.set(section, new Boolean(config.getBoolean(getName(section), null, f.getName(), false)));
+              }
+            }
+            if (f.getType() == Integer.class && f.isAnnotationPresent(Limit.class)) {
+              Object obj = f.get(section);
+              if (obj != null) {
+                Integer val = Integer.class.cast(obj);
+                Limit a = f.getAnnotation(Limit.class);
+                if (a.min() != -1 && val < a.min()) {
+                  val = a.min();
+                }
+                if (a.max() != -1 && val > a.max()) {
+                  val = a.max();
+                }
+                f.set(section, val);
+              }
+            }
+          } catch (Exception ex) {
+            LOGGER.warn("Exception during fromConfig: {}", f.getName());
+          }
+        }
+      }
+    }
+    return section;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/message/MessagePublisher.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/message/MessagePublisher.java
index 3d34ed7..344d4f7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/message/MessagePublisher.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/message/MessagePublisher.java
@@ -21,6 +21,7 @@
 import com.google.inject.assistedinject.Assisted;
 
 import com.googlesource.gerrit.plugins.rabbitmq.config.Properties;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Monitor;
 import com.googlesource.gerrit.plugins.rabbitmq.session.Session;
 import com.googlesource.gerrit.plugins.rabbitmq.session.SessionFactory;
 
@@ -37,6 +38,7 @@
   private final static int MONITOR_FIRSTTIME_DELAY = 15000;
 
   private final Session session;
+  private final Properties properties;
   private final Gson gson;
   private final Timer monitorTimer = new Timer();
   private boolean available = true;
@@ -47,6 +49,7 @@
       SessionFactory sessionFactory,
       Gson gson) {
     this.session = sessionFactory.create(properties);
+    this.properties = properties;
     this.gson = gson;
   }
 
@@ -62,7 +65,7 @@
             session.connect();
           }
         }
-      }, MONITOR_FIRSTTIME_DELAY, session.getProperties().getConnectionMonitorInterval());
+      }, MONITOR_FIRSTTIME_DELAY, properties.getSection(Monitor.class).interval);
       available = true;
     }
   }
@@ -102,7 +105,12 @@
   }
 
   @Override
+  public Properties getProperties() {
+    return properties;
+  }
+
+  @Override
   public String getName() {
-    return session.getProperties().getName();
+    return properties.getName();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/message/Publisher.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/message/Publisher.java
index d88e1e0..77d4962 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/message/Publisher.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/message/Publisher.java
@@ -2,6 +2,7 @@
 
 import com.google.gerrit.common.ChangeListener;
 
+import com.googlesource.gerrit.plugins.rabbitmq.config.Properties;
 import com.googlesource.gerrit.plugins.rabbitmq.session.Session;
 
 public interface Publisher extends ChangeListener {
@@ -11,5 +12,6 @@
   public void disable();
   public boolean isEnable();
   public Session getSession();
+  public Properties getProperties();
   public String getName();
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/session/Session.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/session/Session.java
index 5bd51ef..2416ff6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/session/Session.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/session/Session.java
@@ -16,7 +16,7 @@
 import com.googlesource.gerrit.plugins.rabbitmq.config.Properties;
 
 public interface Session {
-  public Properties getProperties();
+//  public Properties getProperties();
   public boolean isOpen();
   public void connect();
   public void disconnect();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/session/impl/AMQPSession.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/session/impl/AMQPSession.java
index 6d1f6cd..03bf85c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/session/impl/AMQPSession.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/session/impl/AMQPSession.java
@@ -17,21 +17,20 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
-import com.googlesource.gerrit.plugins.rabbitmq.Keys;
 import com.googlesource.gerrit.plugins.rabbitmq.config.Properties;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.AMQP;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Exchange;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Message;
+import com.googlesource.gerrit.plugins.rabbitmq.config.section.Monitor;
 import com.googlesource.gerrit.plugins.rabbitmq.session.Session;
-// import com.rabbitmq.client.AMQP.BasicProperties;
 import com.rabbitmq.client.Channel;
 import com.rabbitmq.client.Connection;
 import com.rabbitmq.client.ConnectionFactory;
-// import com.rabbitmq.client.DefaultConsumer;
-// import com.rabbitmq.client.Envelope;
 import com.rabbitmq.client.ShutdownListener;
 import com.rabbitmq.client.ShutdownSignalException;
 
 import org.apache.commons.codec.CharEncoding;
 import org.apache.commons.lang.StringUtils;
-import org.apache.commons.lang.builder.ToStringBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -56,11 +55,6 @@
   }
 
   @Override
-  public Properties getProperties() {
-    return properties;
-  }
-
-  @Override
   public boolean isOpen() {
     if (connection != null) {
       return true;
@@ -82,7 +76,7 @@
         LOGGER.warn(MSG("Failed to open publish channel."));
         failureCount++;
       }
-      if (failureCount > properties.getConnectionMonitorInterval()) {
+      if (failureCount > properties.getSection(Monitor.class).failureCount) {
         LOGGER.warn("Connection has something wrong. So will be disconnected.");
         disconnect();
       }
@@ -96,23 +90,24 @@
       LOGGER.info(MSG("Already connected."));
       return;
     }
-    LOGGER.info(MSG("Connect to {}..."), properties.getString(Keys.AMQP_URI));
+    AMQP amqp = properties.getSection(AMQP.class);
+    LOGGER.info(MSG("Connect to {}..."), amqp.uri);
     ConnectionFactory factory = new ConnectionFactory();
     try {
-      if (StringUtils.isNotEmpty(properties.getString(Keys.AMQP_URI))) {
-        factory.setUri(properties.getString(Keys.AMQP_URI));
-        if (StringUtils.isNotEmpty(properties.getString(Keys.AMQP_USERNAME))) {
-          factory.setUsername(properties.getString(Keys.AMQP_USERNAME));
+      if (StringUtils.isNotEmpty(amqp.uri)) {
+        factory.setUri(amqp.uri);
+        if (StringUtils.isNotEmpty(amqp.username)) {
+          factory.setUsername(amqp.username);
         }
-        if (StringUtils.isNotEmpty(properties.getString(Keys.AMQP_PASSWORD))) {
-          factory.setPassword(properties.getString(Keys.AMQP_PASSWORD));
+        if (StringUtils.isNotEmpty(amqp.password)) {
+          factory.setPassword(amqp.password);
         }
         connection = factory.newConnection();
         connection.addShutdownListener(this);
         LOGGER.info(MSG("Connection established."));
       }
     } catch (URISyntaxException ex) {
-      LOGGER.error(MSG("URI syntax error: {}"), properties.getString(Keys.AMQP_URI));
+      LOGGER.error(MSG("URI syntax error: {}"), amqp.uri);
     } catch (IOException ex) {
       LOGGER.error(MSG("Connection cannot be opened."));
     } catch (Exception ex) {
@@ -136,17 +131,18 @@
   }
 
   @Override
-  public void publish(String message) {
+  public void publish(String messageBody) {
     if (channel == null || !channel.isOpen()) {
       channel = getChannel();
     }
     if (channel != null && channel.isOpen()) {
+      Message message = properties.getSection(Message.class);
+      Exchange exchange = properties.getSection(Exchange.class);
       try {
         LOGGER.debug(MSG("Send message."));
-        channel.basicPublish(properties.getString(Keys.EXCHANGE_NAME),
-            properties.getString(Keys.MESSAGE_ROUTINGKEY),
+        channel.basicPublish(exchange.name, message.routingKey,
             properties.getAMQProperties().getBasicProperties(),
-            message.getBytes(CharEncoding.UTF_8));
+            messageBody.getBytes(CharEncoding.UTF_8));
       } catch (Exception ex) {
         LOGGER.warn(MSG("Error when sending meessage."), ex);
       }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/solver/BCSolver.java b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/solver/BCSolver.java
index 8662655..af35a33 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/solver/BCSolver.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/rabbitmq/solver/BCSolver.java
@@ -13,21 +13,18 @@
 // limitations under the License.
 package com.googlesource.gerrit.plugins.rabbitmq.solver;
 
-import static com.googlesource.gerrit.plugins.rabbitmq.PropertiesStore.FILE_EXT;
-import static com.googlesource.gerrit.plugins.rabbitmq.PropertiesStore.SITE_DIR;
+import static com.googlesource.gerrit.plugins.rabbitmq.RabbitMQManager.FILE_EXT;
+import static com.googlesource.gerrit.plugins.rabbitmq.RabbitMQManager.SITE_DIR;
 
 import com.google.gerrit.extensions.annotations.PluginData;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Inject;
-import com.google.inject.Singleton;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.io.IOException;
-import java.nio.file.FileSystemException;
 import java.nio.file.Files;
 import java.nio.file.Path;