diff --git a/.gitignore b/.gitignore
index 32858aa..6d614b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,27 @@
+# Eclipse
+.classpath
+.project
+.settings/
+
+# Intellij
+.idea/
+*.iml
+*.iws
+
+# NetBeans
+nb*.xml
+
+# Sublime
+*.sublime-project
+*.sublime-workspace
+
+# Mac
+.DS_Store
+
+# Maven
+target/
+
+# Java
*.class
# Mobile Tools for Java (J2ME)
@@ -8,5 +32,5 @@
*.war
*.ear
-# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+# Virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
diff --git a/AUTHORS.txt b/AUTHORS.txt
new file mode 100644
index 0000000..8916e2b
--- /dev/null
+++ b/AUTHORS.txt
@@ -0,0 +1,4 @@
+The following people provided their time and effort to building this website (in first name alphabetical order):
+
+Gary Rowe
+Jim Burton
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 32976a3..f7dc03d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -2,20 +2,19 @@ The MIT License (MIT)
Copyright (c) 2014 Bitcoin Solutions Ltd
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index db59a71..80f88d7 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,72 @@
-brit-server
-===========
+## MultiBit BRIT Server
-A Dropwizard implementation of a BRIT server
+This repo contains the source for the MultiBit BRIT server.
+
+From a technical point of view this project uses
+
+* Java - Primary language of the app
+* [Maven](http://maven.apache.org/) - Build system
+* [Dropwizard](http://dropwizard.io) - Self-contained web server
+
+## Branches
+
+We follow the ["master-develop" branching strategy](http://nvie.com/posts/a-successful-git-branching-model/).
+
+This means that the latest release is on the "master" branch (the default) and the latest release candidate is on the "develop" branch.
+Any issues are addressed in feature branches from "develop" and merged in as required.
+
+#### Verify you have Maven 3+
+
+Most IDEs (such as [Intellij Community Edition](http://www.jetbrains.com/idea/download/)) come with support for Maven built in,
+but if not then you may need to [install it manually](http://maven.apache.org/download.cgi).
+
+IDEs such as Eclipse may require the [m2eclipse plugin](http://www.sonatype.org/m2eclipse) to be configured.
+
+To quickly check that you have Maven 3+ installed check on the command line:
+```
+$ mvn --version
+```
+
+#### Manually build and install MultiBit HD BRIT
+
+At present it is necessary to checkout [multibit-hd](https://github.com/bitcoin-solutions/multibit-hd/) and build it manually. You will need to
+use the HEAD of the `develop` branch.
+```
+$ mvn clean install
+```
+
+### Inside an IDE
+
+Import the project as a Maven project in the usual manner.
+
+To start the project you just need to execute `BritService.main()` as a Java application. You'll need a runtime configuration
+that passes in `server brit-config.yml` as the Program Arguments.
+
+On startup you will need to provide the passphrase for the Matcher key store. It is not persisted anywhere.
+
+Open a browser to [http://localhost:9090/brit/public-key](http://localhost:9090/brit/public-key) and you should see the BRIT server
+public key.
+
+### Outside of an IDE
+
+Assuming that you've got Java and Maven installed you'll find it very straightforward to get the BRIT server running. Just clone
+from GitHub and do the following:
+
+```
+cd
+mvn clean install
+java -jar target/site-.jar server brit-config.yml
+```
+
+where `` is the root directory of the project as checked out through git and `` is the version
+as found in `pom.xml` (e.g. "3.0.0") but you'll see a `.jar` in the `target` directory so it'll be obvious.
+
+All commands will work on *nix without modification, use \ instead of / for Windows.
+
+Open a browser to [http://localhost:9090/brit/public-key](http://localhost:9090/brit/public-key) and you should see the BRIT server
+public key.
+
+### Where does the ASCII art come from?
+
+The ASCII art for the startup banner was created using the online tool available at
+[TAAG](http://patorjk.com/software/taag/#p=display&f=Slant&t=BRIT%20Server)
diff --git a/brit-config.yml b/brit-config.yml
new file mode 100644
index 0000000..49d18a9
--- /dev/null
+++ b/brit-config.yml
@@ -0,0 +1,51 @@
+# ################################## Dropwizard specific settings ##################################
+
+# Define the HTTP settings
+http:
+ port: 9090
+ adminPort: 9091
+
+logging:
+
+ level: WARN
+
+ # Logger-specific levels.
+ loggers:
+
+ # Set specific levels
+ #"com.sun.jersey.api.client": DEBUG
+ "com.yammer": INFO # See the banner
+ "org.multibit.hd": DEBUG
+
+ # ...
+ # Settings for logging to stdout.
+ console:
+
+ # If true, write log statements to stdout.
+ enabled: true
+
+ # Do not display log statements below this threshold to stdout.
+ threshold: ALL
+
+ # Settings for logging to a file.
+ file:
+
+ # If true, write log statements to a file.
+ enabled: false
+
+ # Do not write log statements below this threshold to the file.
+ threshold: ALL
+
+ # The file to which current statements will be logged.
+ currentLogFilename: /var/log/brit/developer.log
+
+ # When the log file rotates, the archived log will be renamed to this and gzipped. The
+ # %d is replaced with the previous day (yyyy-MM-dd). Custom rolling windows can be created
+ # by passing a SimpleDateFormat-compatible format as an argument: "%d{yyyy-MM-dd-hh}".
+ archivedLogFilenamePattern: /var/log/brit/developer-%d.log.gz
+
+ # The number of archived files to keep.
+ archivedFileCount: 5
+
+ # The timezone used to format dates. HINT: USE THE DEFAULT, UTC.
+ timeZone: UTC
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..a250740
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,122 @@
+
+
+ 4.0.0
+
+ org.multibit.hd
+ brit-server
+
+
+ develop-SNAPSHOT
+
+ http://localhost:9090
+
+ MultiBit BRIT Server
+ A BRIT server using Dropwizard
+ 2014
+
+
+
+ 0.6.2
+ UTF-8
+ UTF-8
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 2.3
+
+ 1.7
+ 1.7
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 1.6
+
+ true
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+ META-INF/*.less
+
+
+
+
+
+
+ package
+
+ shade
+
+
+
+
+
+ org.multibit.hd.brit.BritService
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ com.yammer.dropwizard
+ dropwizard-core
+ ${dropwizard.version}
+
+
+
+ com.yammer.dropwizard
+ dropwizard-views
+ ${dropwizard.version}
+
+
+
+
+ com.google.guava
+ guava
+ 14.0.1
+
+
+
+
+ org.multibit.hd
+ mbhd-brit
+ 0.0.1-SNAPSHOT
+
+
+
+
+
+
+ com.yammer.dropwizard
+ dropwizard-testing
+ ${dropwizard.version}
+ test
+
+
+
+
+
diff --git a/src/main/java/org/multibit/site/BritConfiguration.java b/src/main/java/org/multibit/site/BritConfiguration.java
new file mode 100644
index 0000000..ef08e20
--- /dev/null
+++ b/src/main/java/org/multibit/site/BritConfiguration.java
@@ -0,0 +1,27 @@
+package org.multibit.site;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.yammer.dropwizard.config.Configuration;
+import org.hibernate.validator.constraints.NotEmpty;
+
+/**
+ *
DropWizard Configuration to provide the following to application:
Use java -jar site-develop-SNAPSHOT.jar server site-config.yml to start the demo
+ *
+ * @since 0.0.1
+ *
+ */
+public class BritService extends Service {
+
+ private static final Logger log = LoggerFactory.getLogger(BritService.class);
+
+ /**
+ * The BRIT Matcher root directory
+ */
+ private static final String BRIT_MATCHER_DIRECTORY = "/var/brit/matcher";
+
+ /**
+ * The Matcher
+ */
+ private static Matcher matcher;
+
+ /**
+ * Main entry point to the application
+ *
+ * @param args CLI arguments
+ *
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+
+ // Securely read the password from the console
+ final char[] password = readPassword();
+
+ verifyMatcher(password);
+
+ // Clear the password
+ Arrays.fill(password, ' ');
+
+ Preconditions.checkNotNull(matcher,"'matcher' must be present");
+
+ // Must be OK to be here
+ new BritService().run(args);
+
+ }
+
+ /**
+ * @param password The password for the Matcher secret keyring
+ *
+ * @throws IOException If the Matcher fails to start
+ */
+ private static void verifyMatcher(char[] password) throws IOException {
+
+ final File britMatcherDirectory = new File(BRIT_MATCHER_DIRECTORY);
+ if (!britMatcherDirectory.exists()) {
+ System.err.printf("Matcher directory not present at '%s'.%n", britMatcherDirectory.getAbsolutePath());
+ System.exit(-1);
+ }
+
+ final File matcherStoreDirectory = new File(britMatcherDirectory, "store");
+ if (!matcherStoreDirectory.exists()) {
+ System.err.printf("Store directory not present at '%s'.%n", matcherStoreDirectory.getAbsolutePath());
+ System.exit(-1);
+ }
+
+ File matcherSecretKeyFile = new File(britMatcherDirectory, "gpg/secring.gpg");
+ if (!matcherSecretKeyFile.exists()) {
+ System.err.printf("Matcher secret keyring not present at '%s'.%n", matcherSecretKeyFile.getAbsolutePath());
+ System.exit(-1);
+ }
+
+ // Build the Matcher configuration
+ MatcherConfig matcherConfig = new MatcherConfig(matcherSecretKeyFile, password);
+
+ // Reference the Matcher store
+ MatcherStore matcherStore = MatcherStores.newBasicMatcherStore(matcherStoreDirectory);
+
+ // Build the Matcher
+ matcher = Matchers.newBasicMatcher(matcherConfig, matcherStore);
+
+ // Load the matcher addresses
+
+ }
+
+ private static char[] readPassword() {
+
+ Console console = System.console();
+ final char[] password;
+ if (console == null) {
+ System.err.println("Could not obtain a console. Assuming an IDE and test data.");
+ password = "password".toCharArray();
+ } else {
+ password = console.readPassword("[%s]", "Password:");
+ if (password == null) {
+ System.err.println("Could not read the password.");
+ System.exit(-1);
+ }
+ }
+
+ return password;
+
+ }
+
+ private BritService() {
+
+ }
+
+ @Override
+ public void initialize(Bootstrap bootstrap) {
+
+ // Do nothing
+
+ }
+
+ @Override
+ public void run(BritConfiguration britConfiguration, Environment environment) throws Exception {
+
+ log.info("Scanning environment...");
+
+ // Configure environment
+ environment.scanPackagesForResourcesAndProviders(PublicBritResource.class);
+
+ // Health checks
+ environment.addHealthCheck(new SiteHealthCheck());
+
+ // Providers
+ environment.addProvider(new ViewMessageBodyWriter());
+
+ // Filters
+ environment.addFilter(new SafeLocaleFilter(), "/*");
+
+ // Session handler
+ environment.setSessionHandler(new SessionHandler());
+
+ }
+
+}
diff --git a/src/main/java/org/multibit/site/caches/InMemoryArtifactCache.java b/src/main/java/org/multibit/site/caches/InMemoryArtifactCache.java
new file mode 100644
index 0000000..175f2f1
--- /dev/null
+++ b/src/main/java/org/multibit/site/caches/InMemoryArtifactCache.java
@@ -0,0 +1,87 @@
+package org.multibit.site.caches;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+
+/**
+ *
Cache to provide the following to resources:
+ *
+ *
In-memory thread-safe cache for long-lived artifacts generated periodically
+ *
+ *
+ * @since 3.1.0
+ *
+ */
+public enum InMemoryArtifactCache {
+
+ // Provide a global singleton for the application
+ INSTANCE;
+
+ public static final String SITE_MAP_KEY = "/views/sitemap.xml";
+ public static final String ATOM_FEED_KEY = "/views/atom.xml";
+
+ // A lot of threads will hit this cache
+ private volatile Cache pageCache;
+
+ InMemoryArtifactCache() {
+ reset();
+ }
+
+ /**
+ * Resets the cache and allows the expiry time to be set (perhaps for testing)
+ */
+ public InMemoryArtifactCache reset() {
+
+ // Build the cache
+ if (pageCache != null) {
+ pageCache.invalidateAll();
+ }
+
+ // Store a few items permanently
+ pageCache = CacheBuilder
+ .newBuilder()
+ .maximumSize(100)
+ .build();
+
+ return INSTANCE;
+ }
+
+ /**
+ * @param resourcePath The resource path to locate the Freemarker template
+ *
+ * @return The matching ClientUser or absent
+ */
+ public Optional getByResourcePath(String resourcePath) {
+
+ // Turn off page caching for development here
+ /*
+ if (true) {
+ return Optional.absent();
+ }
+ */
+
+ // Check the cache
+ Optional viewOptional = Optional.fromNullable(pageCache.getIfPresent(resourcePath));
+
+ if (viewOptional.isPresent()) {
+ // Ensure we refresh the cache on a check to maintain the session timeout
+ pageCache.put(resourcePath, viewOptional.get());
+ }
+
+ return viewOptional;
+
+ }
+
+ /**
+ * @param resourcePath The resource path acting as the
+ * @param content The rendered content to cache
+ */
+ public void put(String resourcePath, String content) {
+ Preconditions.checkNotNull(content);
+ Preconditions.checkNotNull(resourcePath);
+ pageCache.put(resourcePath, content);
+ }
+
+}
diff --git a/src/main/java/org/multibit/site/caches/InMemoryAssetCache.java b/src/main/java/org/multibit/site/caches/InMemoryAssetCache.java
new file mode 100644
index 0000000..c1a6f53
--- /dev/null
+++ b/src/main/java/org/multibit/site/caches/InMemoryAssetCache.java
@@ -0,0 +1,92 @@
+package org.multibit.site.caches;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ *
Cache to provide the following to resources:
+ *
+ *
In-memory thread-safe cache for page view instances that may expire
+ *
+ *
+ * @since 0.0.1
+ *
+ */
+public enum InMemoryAssetCache {
+
+ // Provide a global singleton for the application
+ INSTANCE;
+
+ // A lot of threads will hit this cache
+ private volatile Cache pageCache;
+
+ private boolean noCaching=false;
+
+ InMemoryAssetCache() {
+ reset(false);
+ }
+
+ /**
+ * Resets the cache and allows the expiry time to be set (perhaps for testing)
+ */
+ public InMemoryAssetCache reset(boolean noCaching) {
+
+ // Build the cache
+ if (pageCache != null) {
+ pageCache.invalidateAll();
+ }
+
+ // Provide a simple protection against periods of high activity
+ // while allowing a developer to make progress with changes
+ pageCache = CacheBuilder
+ .newBuilder()
+ .maximumSize(100)
+ .expireAfterAccess(5, TimeUnit.SECONDS)
+ .build();
+
+ this.noCaching = noCaching;
+
+ return INSTANCE;
+ }
+
+ /**
+ * @param resourcePath The resource path to locate the Freemarker template
+ *
+ * @return The matching ClientUser or absent
+ */
+ public Optional getByResourcePath(String resourcePath) {
+
+ // Turn off page caching for development here
+ /*
+ if (true) {
+ return Optional.absent();
+ }
+ */
+
+ // Check the cache
+ Optional viewOptional = Optional.fromNullable(pageCache.getIfPresent(resourcePath));
+
+ if (viewOptional.isPresent()) {
+ // Ensure we refresh the cache on a check to maintain the session timeout
+ pageCache.put(resourcePath, viewOptional.get());
+ }
+
+ return viewOptional;
+
+ }
+
+ /**
+ * @param resourcePath The resource path acting as the
+ * @param content The rendered content to cache
+ */
+ public void put(String resourcePath, String content) {
+ Preconditions.checkNotNull(content);
+ Preconditions.checkNotNull(resourcePath);
+ pageCache.put(resourcePath, content);
+ }
+
+}
diff --git a/src/main/java/org/multibit/site/health/MatcherHealthCheck.java b/src/main/java/org/multibit/site/health/MatcherHealthCheck.java
new file mode 100644
index 0000000..8684c5b
--- /dev/null
+++ b/src/main/java/org/multibit/site/health/MatcherHealthCheck.java
@@ -0,0 +1,27 @@
+package org.multibit.site.health;
+
+import com.yammer.metrics.core.HealthCheck;
+
+/**
+ *
HealthCheck to provide the following to application:
+ *
+ *
Provision of checks against the current Matcher
+ *
+ *
+ * @since 0.0.1
+ *
+ */
+public class MatcherHealthCheck extends HealthCheck {
+
+ public MatcherHealthCheck() {
+ super("Matcher health check");
+ }
+
+ @Override
+ protected Result check() throws Exception {
+
+ // TODO Determine best way of detecting a Matcher failure
+
+ return Result.healthy();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/multibit/site/health/SiteHealthCheck.java b/src/main/java/org/multibit/site/health/SiteHealthCheck.java
new file mode 100644
index 0000000..51bb29d
--- /dev/null
+++ b/src/main/java/org/multibit/site/health/SiteHealthCheck.java
@@ -0,0 +1,26 @@
+package org.multibit.site.health;
+
+/**
+ *
HealthCheck to provide the following to application:
+ *
+ *
Provision of checks against a given Configuration property
+ *
+ *
+ * @since 0.0.1
+ *
+ */
+
+import com.yammer.metrics.core.HealthCheck;
+
+public class SiteHealthCheck extends HealthCheck {
+
+ public SiteHealthCheck() {
+ super("Site health check");
+ }
+
+ @Override
+ protected Result check() throws Exception {
+ // TODO Add environment checks
+ return Result.healthy();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/multibit/site/resources/BaseResource.java b/src/main/java/org/multibit/site/resources/BaseResource.java
new file mode 100644
index 0000000..167fdba
--- /dev/null
+++ b/src/main/java/org/multibit/site/resources/BaseResource.java
@@ -0,0 +1,62 @@
+package org.multibit.site.resources;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.Locale;
+
+/**
+ *
Abstract base class to provide the following to subclasses:
+ *
+ *
Provision of common methods
+ *
+ *
+ * @since 0.0.1
+ *
+ */
+public abstract class BaseResource {
+
+ protected static final String DEFAULT_LANGUAGE = "en";
+
+ /**
+ * Jersey creates a fresh resource every request so this is safe
+ */
+ @Context
+ protected UriInfo uriInfo;
+
+ /**
+ * Jersey creates a fresh resource every request so this is safe
+ */
+ @Context
+ protected HttpHeaders httpHeaders;
+
+ /**
+ * @return The most appropriate locale for the upstream request (never null)
+ */
+ public Locale getLocale() {
+ // TODO This should be a configuration setting
+ Locale defaultLocale = Locale.UK;
+
+ Locale locale;
+ if (httpHeaders == null) {
+ locale = defaultLocale;
+ } else {
+ locale = httpHeaders.getLanguage();
+ if (locale == null) {
+ locale = defaultLocale;
+ }
+ }
+ return locale;
+ }
+
+ public WebApplicationException badRequest() {
+ return new WebApplicationException(Response.Status.BAD_REQUEST);
+ }
+
+ public WebApplicationException notFound() {
+ return new WebApplicationException(Response.Status.NOT_FOUND);
+ }
+
+}
diff --git a/src/main/java/org/multibit/site/resources/PublicBritResource.java b/src/main/java/org/multibit/site/resources/PublicBritResource.java
new file mode 100644
index 0000000..4b809ca
--- /dev/null
+++ b/src/main/java/org/multibit/site/resources/PublicBritResource.java
@@ -0,0 +1,63 @@
+package org.multibit.site.resources;
+
+import com.yammer.dropwizard.jersey.caching.CacheControl;
+import com.yammer.metrics.annotation.Timed;
+import org.multibit.site.utils.StreamUtils;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import java.io.IOException;
+
+/**
+ *
Resource to provide the following to application:
+ *
+ *
Provision of BRIT responses
+ *
+ *
+ * @since 0.0.1
+ */
+@Path("/brit")
+@Produces(MediaType.TEXT_PLAIN)
+@Consumes(MediaType.TEXT_PLAIN)
+public class PublicBritResource extends BaseResource {
+
+ /**
+ * Allow a Payer to compare or obtain the matcher public key
+ *
+ * @return A localised view containing HTML
+ */
+ @GET
+ @Path("/public-key")
+ @Timed
+ @CacheControl(noCache = true)
+ public Response getPublicKey() throws IOException {
+
+ String matcherPublicKey = StreamUtils.toString(PublicBritResource.class.getResourceAsStream("/brit/matcher-pubkey.asc"));
+
+ return Response
+ .ok(matcherPublicKey)
+ .build();
+
+ }
+
+ /**
+ * Allow a Payer to submit their wallet ID
+ *
+ * @return A localised view containing HTML
+ */
+ @POST
+ @Timed
+ @CacheControl(noCache = true)
+ public Response submitWalletId(String message) {
+
+
+ return Response
+ .created(UriBuilder.fromPath("/brit").build())
+ .entity(message)
+ .build();
+
+ }
+
+}
diff --git a/src/main/java/org/multibit/site/resources/ResourcePreconditions.java b/src/main/java/org/multibit/site/resources/ResourcePreconditions.java
new file mode 100644
index 0000000..21869f3
--- /dev/null
+++ b/src/main/java/org/multibit/site/resources/ResourcePreconditions.java
@@ -0,0 +1,85 @@
+package org.multibit.site.resources;
+
+import com.google.common.base.Optional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+
+/**
+ *
Specialised preconditions for use in Resources
+ */
+public final class ResourcePreconditions {
+
+ private static final Logger log = LoggerFactory.getLogger(ResourcePreconditions.class);
+
+ /**
+ * Fails if the object is null
+ *
+ * @param obj Object to test
+ * @param fieldName The field name for logging
+ */
+ public static void assertNotNull(Object obj, String fieldName) {
+ if (obj == null) {
+ log.warn("Field '{}' should not be null", fieldName);
+ throw new WebApplicationException(Response.Status.BAD_REQUEST);
+ }
+ }
+
+ /**
+ * Fails if the object is not positive (0 or greater)
+ *
+ * @param obj Object to test
+ * @param fieldName The field name for logging
+ */
+ public static void assertPositive(Number obj, String fieldName) {
+ assertNotNull(obj, fieldName);
+ if (obj.intValue() < 0) {
+ log.warn("Field '{}' should be positive", fieldName);
+ throw new WebApplicationException(Response.Status.BAD_REQUEST);
+ }
+ }
+
+ /**
+ * Fails if the object is absent
+ *
+ * @param obj Object to test
+ * @param fieldName The field name for logging
+ */
+ public static void assertPresent(Optional obj, String fieldName) {
+ assertNotNull(obj, fieldName);
+ if (!obj.isPresent()) {
+ log.warn("Field '{}' should be present", fieldName);
+ throw new WebApplicationException(Response.Status.BAD_REQUEST);
+ }
+ }
+
+ /**
+ * Fails if the object is present
+ *
+ * @param obj Object to test
+ * @param fieldName The field name for logging
+ */
+ public static void assertNotConflicted(Optional obj, String fieldName) {
+ assertNotNull(obj, fieldName);
+ if (obj.isPresent()) {
+ log.warn("Field '{}' should be absent to avoid conflict", fieldName);
+ throw new WebApplicationException(Response.Status.CONFLICT);
+ }
+ }
+
+ /**
+ * Fails if the object is absent
+ *
+ * @param state Conditional that must be true
+ * @param condition The condition message
+ */
+ public static void assertTrue(boolean state, String condition) {
+ if (!state) {
+ log.warn("Condition '{}' should be true", condition);
+ throw new WebApplicationException(Response.Status.BAD_REQUEST);
+ }
+ }
+
+}
diff --git a/src/main/java/org/multibit/site/resources/RuntimeExceptionMapper.java b/src/main/java/org/multibit/site/resources/RuntimeExceptionMapper.java
new file mode 100644
index 0000000..6003a88
--- /dev/null
+++ b/src/main/java/org/multibit/site/resources/RuntimeExceptionMapper.java
@@ -0,0 +1,75 @@
+package org.multibit.site.resources;
+
+import com.sun.jersey.api.core.HttpContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ *
Provider to provide the following to Jersey framework:
+ *
+ * @since 0.0.1
+ *
+ */
+public class BinaryFixtureHelpers {
+
+ private BinaryFixtureHelpers() { /* singleton */ }
+
+ /**
+ * Reads the given fixture file from {@code src/test/resources} and returns its contents as a
+ * byte array.
+ *
+ * @param filename the filename of the fixture file
+ *
+ * @return the contents of {@code src/test/resources/{filename}}
+ *
+ * @throws java.io.IOException if {@code filename} doesn't exist or can't be opened
+ */
+ public static byte[] fixture(String filename) throws IOException {
+ return Resources.toByteArray(BinaryFixtureHelpers.class.getResource(filename));
+ }
+}
diff --git a/src/test/java/org/multibit/site/testing/FixtureAsserts.java b/src/test/java/org/multibit/site/testing/FixtureAsserts.java
new file mode 100644
index 0000000..1987835
--- /dev/null
+++ b/src/test/java/org/multibit/site/testing/FixtureAsserts.java
@@ -0,0 +1,91 @@
+package org.multibit.site.testing;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Charsets;
+import com.google.common.io.Resources;
+
+import java.io.IOException;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+
+/**
+ *
Fixture assertions to provide the following to application:
+ *
+ *
Support methods to simplify common test patterns
+ *
+ *
+ * @since 0.0.1
+ */
+public class FixtureAsserts {
+
+ /**
+ * Validates the fixture JSON and provides a minified String for comparison
+ *
+ * @param fixtureClasspath The classpath (can be in other JARs)
+ *
+ * @return The contents as parsed by JSON
+ *
+ * @throws java.io.IOException If something goes wrong
+ */
+ public static String jsonFixture(String fixtureClasspath) throws IOException {
+ ObjectMapper om = new ObjectMapper();
+ return om.writeValueAsString(om.readValue(fixture(fixtureClasspath),JsonNode.class));
+ }
+
+ /**
+ * @param fixtureClasspath The classpath (can be in other JARs)
+ *
+ * @return A contents as a UTF8 string
+ *
+ * @throws java.io.IOException If something goes wrong
+ */
+ public static String fixture(String fixtureClasspath) throws IOException {
+ return Resources.toString(FixtureAsserts.class.getResource(fixtureClasspath), Charsets.UTF_8).trim();
+ }
+
+ /**
+ * Takes a String and compares it to a JSON fixture (
+ *
+ * @param reason The reason (e.g. "a Customer can be marshalled to JSON")
+ * @param representation The simple string representation
+ * @param fixtureClasspath The classpath reference to the resource (e.g. "fixtures/example.json")
+ *
+ * @throws java.io.IOException If something goes wrong
+ */
+ public static void assertStringMatchesJsonFixture(String reason, String representation, String fixtureClasspath) throws IOException {
+
+ assertThat(jsonFixture(fixtureClasspath)).isEqualTo(representation).describedAs(reason);
+ }
+
+ /**
+ * Takes a String and compares it to a normalised String fixture
+ *
+ * @param reason The reason (e.g. "a Customer can be marshalled to JSON")
+ * @param representation The simple string representation
+ * @param fixtureClasspath The classpath reference to the resource (e.g. "/fixtures/example.json")
+ *
+ * @throws java.io.IOException If something goes wrong
+ */
+ public static void assertStringMatchesStringFixture(String reason, String representation, String fixtureClasspath) throws IOException {
+
+ assertThat(fixture(fixtureClasspath)).isEqualTo(representation).describedAs(reason);
+
+ }
+
+ /**
+ * Compares the given byte[] with that read from the expected fixture
+ *
+ * @param reason The reason (e.g. "a correct Issue has been generated")
+ * @param representation The byte[] to test
+ * @param fixtureClasspath The classpath reference to the resource (e.g. "fixtures/example.png")
+ *
+ * @throws java.io.IOException If something goes wrong
+ */
+ public static void assertRepresentationMatchesBinaryFixture(String reason, byte[] representation, String fixtureClasspath) throws IOException {
+
+ assertThat(BinaryFixtureHelpers.fixture(fixtureClasspath)).isEqualTo(representation).describedAs(reason);
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/resources/fixtures/brit/payer-1.txt b/src/test/resources/fixtures/brit/payer-1.txt
new file mode 100644
index 0000000..5ab2f8a
--- /dev/null
+++ b/src/test/resources/fixtures/brit/payer-1.txt
@@ -0,0 +1 @@
+Hello
\ No newline at end of file