From e6469906db27dcba15f69e0030584a54ef10ed91 Mon Sep 17 00:00:00 2001
From: James
Date: Tue, 15 Nov 2011 21:48:41 -0600
Subject: [PATCH 01/20] let Page objects choose to return a headless-type Reply
object for custom status codes and the other goodies of Reply
---
.../sitebricks/example/PageWithReply.java | 13 ++++++++
.../sitebricks/example/SitebricksConfig.java | 1 +
.../src/main/resources/PageWithReply.html | 5 ++++
.../PageWithReplyAcceptanceTest.java | 23 ++++++++++++++
.../routing/WidgetRoutingDispatcher.java | 30 +++++++++++--------
5 files changed, 60 insertions(+), 12 deletions(-)
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/PageWithReply.java
create mode 100644 sitebricks-acceptance-tests/src/main/resources/PageWithReply.html
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/PageWithReplyAcceptanceTest.java
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/PageWithReply.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/PageWithReply.java
new file mode 100644
index 00000000..4ff0830b
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/PageWithReply.java
@@ -0,0 +1,13 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.headless.Reply;
+import com.google.sitebricks.http.Get;
+
+public class PageWithReply {
+
+ @Get
+ public Object get() {
+ return Reply.saying().status(678);
+
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/SitebricksConfig.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/SitebricksConfig.java
index f86a0b62..fa6242be 100644
--- a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/SitebricksConfig.java
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/SitebricksConfig.java
@@ -79,6 +79,7 @@ private void bindExplicitly() {
at("/repeat").show(Repeat.class);
at("/showif").show(ShowIf.class);
at("/dynamic.js").show(DynamicJs.class);
+ at("/pageWithReply").show(PageWithReply.class);
at("/conversion").show(Conversion.class);
diff --git a/sitebricks-acceptance-tests/src/main/resources/PageWithReply.html b/sitebricks-acceptance-tests/src/main/resources/PageWithReply.html
new file mode 100644
index 00000000..3ab84e84
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/PageWithReply.html
@@ -0,0 +1,5 @@
+
+
+for this test, the template will never be displayed because the page get method will always return a non-200 status code Reply
+
+
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/PageWithReplyAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/PageWithReplyAcceptanceTest.java
new file mode 100644
index 00000000..ca807f51
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/PageWithReplyAcceptanceTest.java
@@ -0,0 +1,23 @@
+package com.google.sitebricks.acceptance;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import org.testng.annotations.Test;
+
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+
+@Test(suiteName = AcceptanceTest.SUITE)
+public class PageWithReplyAcceptanceTest {
+
+ public void shouldReturnCustomStatusCode() throws IOException {
+ URL url = new URL(AcceptanceTest.BASE_URL + "/pageWithReply");
+
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ int expected = 678;
+ int actual = connection.getResponseCode();
+
+ assert actual == expected : "expected custom response code '" + expected + "' but was '" + actual + "'";
+ }
+}
diff --git a/sitebricks/src/main/java/com/google/sitebricks/routing/WidgetRoutingDispatcher.java b/sitebricks/src/main/java/com/google/sitebricks/routing/WidgetRoutingDispatcher.java
index 9a19f9cc..1ed1b4e8 100644
--- a/sitebricks/src/main/java/com/google/sitebricks/routing/WidgetRoutingDispatcher.java
+++ b/sitebricks/src/main/java/com/google/sitebricks/routing/WidgetRoutingDispatcher.java
@@ -1,5 +1,9 @@
package com.google.sitebricks.routing;
+import java.io.IOException;
+
+import net.jcip.annotations.Immutable;
+
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -8,12 +12,10 @@
import com.google.sitebricks.binding.FlashCache;
import com.google.sitebricks.binding.RequestBinder;
import com.google.sitebricks.headless.HeadlessRenderer;
+import com.google.sitebricks.headless.Reply;
import com.google.sitebricks.headless.Request;
import com.google.sitebricks.rendering.resource.ResourcesService;
import com.google.sitebricks.routing.PageBook.Page;
-import net.jcip.annotations.Immutable;
-
-import java.io.IOException;
/**
* @author Dhanji R. Prasanna (dhanji@gmail.com)
@@ -69,14 +71,12 @@ public Object dispatch(Request request, Events event) throws IOException {
final Object instance = page.instantiate();
if (page.isHeadless()) {
return bindAndReply(request, page, instance);
- } else {
- respond = new StringBuilderRespond(instance);
-
- //fire events and render reponders
- bindAndRespond(request, page, respond, instance);
}
+
+ //fire events and render reponders
+ return bindAndRespond(request, page, instance);
+
- return respond;
}
private Object bindAndReply(Request request, Page page, Object instance) throws IOException {
@@ -87,8 +87,9 @@ private Object bindAndReply(Request request, Page page, Object instance) throws
return fireEvent(request, page, instance);
}
- private void bindAndRespond(Request request, PageBook.Page page, Respond respond,
- Object instance) {
+ private Object bindAndRespond(Request request, PageBook.Page page,
+ Object instance) throws IOException {
+ Respond respond = new StringBuilderRespond(instance);
//bind request
binder.bind(request, instance);
@@ -105,6 +106,9 @@ else if (redirect instanceof Class) {
// should never be null coz it is validated on compile.
respond.redirect(contextualize(request, targetPage.getUri()));
+ } else if (redirect instanceof Reply) {
+ //page wants to be headless
+ return bindAndReply(request, page, instance);
} else {
// Handle page-chaining driven redirection.
PageBook.Page targetPage = book.forInstance(redirect);
@@ -116,8 +120,10 @@ else if (redirect instanceof Class) {
// verified at compile, not be a variablized matcher.
respond.redirect(contextualize(request, targetPage.getUri()));
}
- } else
+ } else {
page.widget().render(instance, respond);
+ }
+ return respond;
}
// We're sure the request parameter map is a Map
From 978ecab335b202bf5f4c68a3e261e3f77c5afb45 Mon Sep 17 00:00:00 2001
From: james
Date: Mon, 12 Dec 2011 09:40:06 -0600
Subject: [PATCH 02/20] sitebricks 0.8.5 tar.gz from github
---
sitebricks-acceptance-tests/pom.xml | 137 +++
.../com/google/sitebricks/example/Case.java | 19 +
.../sitebricks/example/CompileErrors.java | 21 +
.../example/ContentNegotiation.java | 29 +
.../google/sitebricks/example/Conversion.java | 48 ++
.../sitebricks/example/DecoratedPage.java | 16 +
.../sitebricks/example/DecoratorPage.java | 12 +
.../google/sitebricks/example/DynamicJs.java | 17 +
.../com/google/sitebricks/example/Embed.java | 23 +
.../com/google/sitebricks/example/Forms.java | 40 +
.../google/sitebricks/example/HelloWorld.java | 27 +
.../sitebricks/example/HelloWorldService.java | 48 ++
.../sitebricks/example/HiddenFieldMethod.java | 43 +
.../com/google/sitebricks/example/I18n.java | 67 ++
.../example/MvelTemplateExample.java | 17 +
.../google/sitebricks/example/NextPage.java | 21 +
.../google/sitebricks/example/PageChain.java | 26 +
.../example/PostableRestfulWebService.java | 40 +
.../com/google/sitebricks/example/Repeat.java | 31 +
.../sitebricks/example/RestfulWebService.java | 83 ++
.../RestfulWebServiceNoAnnotations.java | 40 +
.../example/RestfulWebServiceWithCRUD.java | 61 ++
.../RestfulWebServiceWithCRUDConversions.java | 196 +++++
.../RestfulWebServiceWithMatrixParams.java | 31 +
.../RestfulWebServiceWithSubpaths.java | 56 ++
.../RestfulWebServiceWithSubpaths2.java | 98 +++
.../sitebricks/example/SelectRouting.java | 118 +++
.../com/google/sitebricks/example/ShowIf.java | 24 +
.../sitebricks/example/SitebricksConfig.java | 145 ++++
.../com/google/sitebricks/example/Start.java | 31 +
.../google/sitebricks/example/StartAware.java | 17 +
.../google/sitebricks/example/TestPage.java | 28 +
.../src/main/resources/Case.html | 71 ++
.../src/main/resources/CompileErrors.html | 61 ++
.../main/resources/ContentNegotiation.html | 8 +
.../src/main/resources/Conversion.html | 73 ++
.../src/main/resources/DecoratedPage.html | 7 +
.../src/main/resources/Decorator.html | 19 +
.../src/main/resources/Embed.html | 48 ++
.../src/main/resources/Forms.html | 88 ++
.../src/main/resources/HelloWorld.html | 67 ++
.../src/main/resources/HiddenFieldMethod.html | 83 ++
.../src/main/resources/I18n.html | 68 ++
.../main/resources/MvelTemplateExample.mvel | 52 ++
.../src/main/resources/NextPage.html | 52 ++
.../src/main/resources/PageChain.html | 64 ++
.../src/main/resources/Repeat.html | 73 ++
.../src/main/resources/SelectRouting.html | 228 +++++
.../src/main/resources/ShowIf.html | 62 ++
.../src/main/resources/TestPage.html | 8 +
.../src/main/resources/WEB-INF/web.xml | 22 +
.../src/main/resources/default.css | 339 ++++++++
.../src/main/resources/dynamic.js | 3 +
.../src/main/resources/index.html | 69 ++
.../ServletContainerIntegrationTest.java | 48 ++
.../acceptance/CaseAcceptanceTest.java | 19 +
.../acceptance/ConnegAcceptanceTest.java | 41 +
.../acceptance/ConversionAcceptanceTest.java | 31 +
.../acceptance/DecoratorAcceptanceTest.java | 25 +
.../acceptance/DynamicJsAcceptanceTest.java | 20 +
.../acceptance/EmbedAcceptanceTest.java | 21 +
.../acceptance/FormsAcceptanceTest.java | 31 +
.../acceptance/HelloWorldAcceptanceTest.java | 41 +
.../HiddenFieldMethodAcceptanceTest.java | 25 +
.../acceptance/I18nAcceptanceTest.java | 25 +
.../acceptance/JettyAcceptanceTest.java | 40 +
.../PageChainingAcceptanceTest.java | 31 +
...ostableRestfuWebServiceAcceptanceTest.java | 49 ++
.../acceptance/RepeatAcceptanceTest.java | 40 +
.../RestfuWebServiceAcceptanceTest.java | 99 +++
...estfuWebServiceWithCRUDAcceptanceTest.java | 104 +++
...viceWithCRUDConversionsAcceptanceTest.java | 111 +++
...ServiceWithMatrixParamsAcceptanceTest.java | 57 ++
...WebServiceWithSubpaths2AcceptanceTest.java | 167 ++++
...uWebServiceWithSubpathsAcceptanceTest.java | 112 +++
.../SelectRoutingAcceptanceTest.java | 254 ++++++
...WebServiceWithSubpaths2AcceptanceTest.java | 52 ++
.../acceptance/StatsAcceptanceTest.java | 24 +
.../sitebricks/acceptance/page/CasePage.java | 29 +
.../acceptance/page/ConnegPage.java | 45 +
.../acceptance/page/ConversionPage.java | 95 +++
.../acceptance/page/DecoratorPage.java | 36 +
.../acceptance/page/DynamicJsPage.java | 24 +
.../sitebricks/acceptance/page/EmbedPage.java | 32 +
.../sitebricks/acceptance/page/FormsPage.java | 54 ++
.../acceptance/page/HelloWorldPage.java | 38 +
.../page/HiddenFieldMethodPage.java | 42 +
.../sitebricks/acceptance/page/I18nPage.java | 45 +
.../acceptance/page/PageChainPage.java | 31 +
.../acceptance/page/RepeatPage.java | 49 ++
.../acceptance/page/SelectRoutingPage.java | 44 +
.../sitebricks/acceptance/page/StatsPage.java | 32 +
.../acceptance/util/AcceptanceTest.java | 16 +
.../sitebricks/acceptance/util/Jetty.java | 45 +
sitebricks-client/pom.xml | 60 ++
.../sitebricks/client/AHCWebClient.java | 152 ++++
.../google/sitebricks/client/CommonsWeb.java | 28 +
.../google/sitebricks/client/Transport.java | 48 ++
.../sitebricks/client/TransportException.java | 10 +
.../com/google/sitebricks/client/Web.java | 29 +
.../google/sitebricks/client/WebClient.java | 19 +
.../sitebricks/client/WebClientBuilder.java | 72 ++
.../google/sitebricks/client/WebResponse.java | 20 +
.../sitebricks/client/WebResponseImpl.java | 89 ++
.../client/transport/ByteArrayTransport.java | 24 +
.../transport/JacksonJsonTransport.java | 169 ++++
.../sitebricks/client/transport/Json.java | 18 +
.../sitebricks/client/transport/Raw.java | 18 +
.../client/transport/SimpleTextTransport.java | 24 +
.../sitebricks/client/transport/Text.java | 18 +
.../client/transport/XStreamXmlTransport.java | 28 +
.../sitebricks/client/transport/Xml.java | 18 +
.../client/WebClientEdslIntegrationTest.java | 43 +
.../client/WebClientIntegrationTest.java | 24 +
.../client/transport/RawTransportTest.java | 39 +
.../transport/SimpleTextTransportTest.java | 37 +
.../client/transport/XmlTransportTest.java | 78 ++
.../com/google/sitebricks/client/tweet.json | 19 +
sitebricks-converter/pom.xml | 44 +
.../sitebricks/conversion/Converter.java | 18 +
.../conversion/ConverterAdaptor.java | 11 +
.../conversion/ConverterRegistry.java | 15 +
.../sitebricks/conversion/ConverterUtils.java | 34 +
.../sitebricks/conversion/DateConverters.java | 170 ++++
.../conversion/DummyTypeConverter.java | 19 +
.../conversion/MvelConversionHandlers.java | 69 ++
.../conversion/MvelTypeConverter.java | 54 ++
.../conversion/NumberConverters.java | 53 ++
.../conversion/ObjectToStringConverter.java | 12 +
.../conversion/SingletonListConverter.java | 17 +
.../conversion/StandardTypeConverter.java | 219 +++++
.../StringToPrimitiveConverters.java | 50 ++
.../sitebricks/conversion/TypeConverter.java | 23 +
.../conversion/generics/CaptureType.java | 35 +
.../conversion/generics/CaptureTypeImpl.java | 71 ++
.../generics/GenericArrayTypeImpl.java | 64 ++
.../conversion/generics/Generics.java | 547 ++++++++++++
.../generics/ParameterizedTypeImpl.java | 95 +++
.../conversion/generics/TypeToken.java | 97 +++
.../conversion/generics/VarMap.java | 94 +++
.../conversion/generics/WildcardTypeImpl.java | 67 ++
.../conversion/MvelTypeConverterTest.java | 14 +
.../conversion/StandardTypeConverterTest.java | 97 +++
.../conversion/TestTypeConverter.java | 15 +
sitebricks-jetty-archetype/pom.xml | 39 +
.../info/sitebricks/example/AppConfig.java | 29 +
.../java/info/sitebricks/example/Main.java | 25 +
.../info/sitebricks/example/web/HomePage.java | 32 +
.../src/main/resources/HomePage.html | 14 +
.../src/main/resources/WEB-INF/web.xml | 30 +
sitebricks-mail/pom.xml | 81 ++
.../sitebricks/mail/CommandCompletion.java | 51 ++
.../sitebricks/mail/FolderObserver.java | 21 +
.../java/com/google/sitebricks/mail/Mail.java | 24 +
.../google/sitebricks/mail/MailClient.java | 87 ++
.../sitebricks/mail/MailClientConfig.java | 49 ++
.../sitebricks/mail/MailClientHandler.java | 109 +++
.../mail/MailClientPipelineFactory.java | 50 ++
.../sitebricks/mail/NettyImapClient.java | 205 +++++
.../sitebricks/mail/SitebricksMail.java | 65 ++
.../java/com/google/sitebricks/mail/example | 20 +
.../google/sitebricks/mail/imap/Command.java | 43 +
.../sitebricks/mail/imap/Extractor.java | 12 +
.../com/google/sitebricks/mail/imap/Flag.java | 18 +
.../google/sitebricks/mail/imap/Folder.java | 27 +
.../sitebricks/mail/imap/FolderExtractor.java | 36 +
.../sitebricks/mail/imap/FolderStatus.java | 55 ++
.../mail/imap/FolderStatusExtractor.java | 42 +
.../mail/imap/ListFoldersExtractor.java | 40 +
.../google/sitebricks/mail/imap/Message.java | 11 +
.../mail/imap/MessageExtractor.java | 69 ++
.../sitebricks/mail/imap/MessageStatus.java | 71 ++
.../mail/imap/MessageStatusExtractor.java | 202 +++++
.../mail/MailClientIntegrationTest.java | 62 ++
.../mail/imap/MessageStatusExtractorTest.java | 45 +
.../mail/test/integration/TestImap.java | 24 +
.../google/sitebricks/mail/webapp/Home.java | 13 +
.../sitebricks/mail/webapp/WebConfig.java | 21 +
sitebricks-mail/src/test/resources/Home.html | 10 +
.../src/test/resources/WEB-INF/web.xml | 22 +
.../sitebricks/mail/imap/fetch_all_data.txt | 9 +
sitebricks-options/pom.xml | 66 ++
.../google/sitebricks/options/Options.java | 18 +
.../sitebricks/options/OptionsModule.java | 192 +++++
.../sitebricks/options/OptionsTest.java | 141 ++++
.../sitebricks/options/options.properties | 3 +
sitebricks-web/pom.xml | 69 ++
.../src/main/java/info/sitebricks/Jetty.java | 45 +
.../info/sitebricks/SitebricksConfig.java | 32 +
.../java/info/sitebricks/data/Document.java | 79 ++
.../main/java/info/sitebricks/data/Index.java | 53 ++
.../info/sitebricks/persist/PersistAware.java | 31 +
.../sitebricks/persist/PersistFilter.java | 46 ++
.../info/sitebricks/persist/StoreModule.java | 17 +
.../info/sitebricks/persist/WikiStore.java | 67 ++
.../main/java/info/sitebricks/web/Home.java | 50 ++
.../java/info/sitebricks/web/WikiService.java | 50 ++
sitebricks-web/src/main/resources/Home.html | 52 ++
.../src/main/resources/WEB-INF/web.xml | 21 +
.../info/sitebricks/data/Document.html | 15 +
.../src/main/resources/js/jquery-1.4.3.min.js | 166 ++++
sitebricks-web/src/main/resources/js/json2.js | 324 ++++++++
sitebricks-web/src/main/resources/js/rpc.js | 41 +
.../src/main/resources/js/sitebricks.js | 46 ++
sitebricks-web/src/main/resources/main.css | 255 ++++++
sitebricks/TODO | 18 +
sitebricks/pom.atom | 23 +
sitebricks/pom.xml | 140 ++++
.../google/sitebricks/ActionDescriptor.java | 94 +++
.../main/java/com/google/sitebricks/At.java | 15 +
.../java/com/google/sitebricks/Aware.java | 10 +
.../com/google/sitebricks/AwareModule.java | 26 +
.../com/google/sitebricks/Bootstrapper.java | 16 +
.../java/com/google/sitebricks/Bricks.java | 14 +
.../java/com/google/sitebricks/Classes.java | 186 +++++
.../google/sitebricks/DebugModePageBook.java | 125 +++
.../DebugModeRoutingDispatcher.java | 125 +++
.../java/com/google/sitebricks/Evaluator.java | 17 +
.../java/com/google/sitebricks/Export.java | 25 +
.../java/com/google/sitebricks/GaeModule.java | 23 +
.../google/sitebricks/HiddenMethodFilter.java | 101 +++
.../java/com/google/sitebricks/Localizer.java | 262 ++++++
.../sitebricks/MissingTemplateException.java | 10 +
.../com/google/sitebricks/MvelEvaluator.java | 70 ++
.../sitebricks/NoSuchResourceException.java | 14 +
.../PackageScanFailedException.java | 11 +
.../com/google/sitebricks/PageBinder.java | 61 ++
.../com/google/sitebricks/Renderable.java | 18 +
.../java/com/google/sitebricks/Respond.java | 45 +
.../ScanAndCompileBootstrapper.java | 283 +++++++
.../main/java/com/google/sitebricks/Show.java | 16 +
.../com/google/sitebricks/Shutdowner.java | 27 +
.../google/sitebricks/SitebricksFilter.java | 83 ++
.../sitebricks/SitebricksInternalModule.java | 207 +++++
.../google/sitebricks/SitebricksModule.java | 322 ++++++++
.../sitebricks/SitebricksServletModule.java | 87 ++
.../sitebricks/StringBuilderRespond.java | 139 ++++
.../java/com/google/sitebricks/Template.java | 44 +
.../com/google/sitebricks/TemplateLoader.java | 176 ++++
.../sitebricks/TemplateLoadingException.java | 14 +
.../java/com/google/sitebricks/Visible.java | 24 +
.../binding/ConcurrentPropertyCache.java | 44 +
.../binding/CookieBasedFlashCache.java | 97 +++
.../google/sitebricks/binding/FlashCache.java | 18 +
.../sitebricks/binding/GaeFlashCache.java | 42 +
.../binding/HttpSessionFlashCache.java | 32 +
.../binding/InvalidBindingException.java | 10 +
.../sitebricks/binding/MvelRequestBinder.java | 123 +++
.../sitebricks/binding/NoFlashCache.java | 23 +
.../sitebricks/binding/PropertyCache.java | 11 +
.../sitebricks/binding/RequestBinder.java | 15 +
.../sitebricks/compiler/AnalysisError.java | 104 +++
.../sitebricks/compiler/AnalysisErrors.java | 11 +
.../sitebricks/compiler/AnnotationNode.java | 62 ++
.../sitebricks/compiler/AnnotationParser.java | 52 ++
.../sitebricks/compiler/CompileError.java | 126 +++
.../sitebricks/compiler/CompileErrors.java | 19 +
.../sitebricks/compiler/CompiledToken.java | 63 ++
.../google/sitebricks/compiler/Compilers.java | 43 +
.../com/google/sitebricks/compiler/Dom.java | 190 +++++
.../compiler/EvaluatorCompiler.java | 71 ++
.../compiler/ExpressionCompileException.java | 33 +
.../compiler/FlatTemplateCompiler.java | 50 ++
.../sitebricks/compiler/HtmlParser.java | 562 +++++++++++++
.../compiler/HtmlTemplateCompiler.java | 673 +++++++++++++++
.../compiler/MvelEvaluatorCompiler.java | 225 +++++
.../google/sitebricks/compiler/Parsing.java | 262 ++++++
.../sitebricks/compiler/RepeatToken.java | 15 +
.../compiler/RequireWidgetInternPool.java | 13 +
.../compiler/StandardCompilers.java | 158 ++++
.../compiler/TemplateCompileException.java | 130 +++
.../compiler/TemplateParseException.java | 10 +
.../com/google/sitebricks/compiler/Token.java | 28 +
.../compiler/XmlTemplateCompiler.java | 505 ++++++++++++
.../template/MvelTemplateCompiler.java | 39 +
.../FreemarkerTemplateCompiler.java | 78 ++
.../google/sitebricks/core/CaseWidget.java | 20 +
.../com/google/sitebricks/core/Repeat.java | 76 ++
.../com/google/sitebricks/core/ShowIf.java | 39 +
.../google/sitebricks/core/package-info.java | 1 +
.../google/sitebricks/debug/DebugPage.html | 33 +
.../google/sitebricks/debug/DebugPage.java | 53 ++
.../sitebricks/headless/HeadlessRenderer.java | 16 +
.../com/google/sitebricks/headless/Reply.java | 136 +++
.../headless/ReplyBasedHeadlessRenderer.java | 34 +
.../sitebricks/headless/ReplyMaker.java | 196 +++++
.../google/sitebricks/headless/Request.java | 79 ++
.../google/sitebricks/headless/Service.java | 49 ++
.../com/google/sitebricks/http/Delete.java | 16 +
.../java/com/google/sitebricks/http/Get.java | 15 +
.../java/com/google/sitebricks/http/Head.java | 15 +
.../java/com/google/sitebricks/http/Post.java | 16 +
.../java/com/google/sitebricks/http/Put.java | 16 +
.../com/google/sitebricks/http/Select.java | 34 +
.../com/google/sitebricks/http/Trace.java | 15 +
.../sitebricks/http/negotiate/Accept.java | 40 +
.../http/negotiate/ConnegModule.java | 15 +
.../http/negotiate/ContentNegotiator.java | 31 +
.../http/negotiate/ExactMatchNegotiator.java | 37 +
.../http/negotiate/Negotiation.java | 16 +
.../http/negotiate/RegexNegotiator.java | 38 +
.../http/negotiate/WildcardNegotiator.java | 100 +++
.../com/google/sitebricks/i18n/Message.java | 14 +
.../sitebricks/rendering/Attributes.java | 17 +
.../sitebricks/rendering/Decorated.java | 18 +
.../google/sitebricks/rendering/EmbedAs.java | 16 +
.../sitebricks/rendering/SelfRendering.java | 14 +
.../google/sitebricks/rendering/Strings.java | 43 +
.../sitebricks/rendering/Templates.java | 85 ++
.../com/google/sitebricks/rendering/With.java | 16 +
.../rendering/control/ArgumentWidget.java | 45 +
.../sitebricks/rendering/control/Chains.java | 23 +
.../rendering/control/ChooseWidget.java | 75 ++
.../rendering/control/DecorateWidget.java | 101 +++
.../control/DefaultWidgetRegistry.java | 164 ++++
.../rendering/control/EmbedWidget.java | 78 ++
.../rendering/control/EmbeddedRespond.java | 132 +++
.../control/EmbeddedRespondFactory.java | 24 +
.../rendering/control/HeaderWidget.java | 46 ++
.../rendering/control/IncludeWidget.java | 30 +
.../control/NoSuchWidgetException.java | 10 +
.../control/ProceedingWidgetChain.java | 52 ++
.../rendering/control/RawTextWidget.java | 28 +
.../rendering/control/RepeatWidget.java | 92 +++
.../rendering/control/RequireWidget.java | 42 +
.../rendering/control/ShowIfWidget.java | 39 +
.../control/SingletonWidgetChain.java | 31 +
.../control/TerminalWidgetChain.java | 30 +
.../rendering/control/TextFieldWidget.java | 36 +
.../rendering/control/TextWidget.java | 43 +
.../rendering/control/WidgetChain.java | 10 +
.../rendering/control/WidgetRegistry.java | 45 +
.../rendering/control/WidgetWrapper.java | 113 +++
.../rendering/control/XmlDirectiveWidget.java | 39 +
.../rendering/control/XmlWidget.java | 137 +++
.../sitebricks/rendering/resource/Assets.java | 27 +
.../resource/ClasspathResourcesService.java | 179 ++++
.../resource/ResourceLoadingException.java | 14 +
.../rendering/resource/ResourcesService.java | 15 +
.../com/google/sitebricks/routing/Action.java | 31 +
.../sitebricks/routing/DefaultPageBook.java | 778 ++++++++++++++++++
.../routing/EventDispatchException.java | 10 +
.../routing/InMemorySystemMetrics.java | 103 +++
.../routing/InvalidEventHandlerException.java | 10 +
.../google/sitebricks/routing/PageBook.java | 142 ++++
.../sitebricks/routing/PathMatcher.java | 14 +
.../sitebricks/routing/PathMatcherChain.java | 141 ++++
.../google/sitebricks/routing/Production.java | 13 +
.../sitebricks/routing/RoutingDispatcher.java | 16 +
.../sitebricks/routing/ServiceAction.java | 32 +
.../google/sitebricks/routing/SpiAction.java | 56 ++
.../sitebricks/routing/SystemMetrics.java | 39 +
.../routing/WidgetRoutingDispatcher.java | 143 ++++
.../google/sitebricks/core/CaseWidget.html | 6 +
.../com/google/sitebricks/core/ll.gif | Bin 0 -> 46 bytes
.../com/google/sitebricks/core/lr.gif | Bin 0 -> 47 bytes
.../com/google/sitebricks/core/ul.gif | Bin 0 -> 46 bytes
.../com/google/sitebricks/core/ur.gif | Bin 0 -> 47 bytes
.../rendering/resource/mimetypes.properties | 9 +
.../google/sitebricks/templates.properties | 2 +
.../java/com/google/sitebricks/EdslTest.java | 70 ++
.../google/sitebricks/LocalizationTest.java | 289 +++++++
.../com/google/sitebricks/RespondTest.java | 30 +
.../sitebricks/RespondersForTesting.java | 13 +
.../google/sitebricks/TemplateLoaderTest.java | 83 ++
.../google/sitebricks/WidgetFilterTest.java | 156 ++++
.../binding/MvelRequestBinderTest.java | 214 +++++
.../sitebricks/binding/PropertyCacheTest.java | 21 +
.../FreemarkerTemplateCompilerTest.java | 422 ++++++++++
.../compiler/HtmlTemplateCompilerTest.java | 486 +++++++++++
.../compiler/XmlLineNumberParsingTest.java | 45 +
.../compiler/XmlTemplateCompilerTest.java | 459 +++++++++++
.../headless/HeadlessReplyTest.java | 230 ++++++
.../sitebricks/headless/ReplyEdslTest.java | 59 ++
.../negotiate/ExactMatchNegotiatorTest.java | 79 ++
.../http/negotiate/RegexNegotiatorTest.java | 104 +++
.../negotiate/WildcardNegotiatorTest.java | 124 +++
.../DynTypedMvelEvaluatorCompiler.java | 71 ++
.../rendering/EvaluatorCompilerTest.java | 260 ++++++
.../rendering/MvelGenericsConfidenceTest.java | 67 ++
.../sitebricks/rendering/ParsingTest.java | 76 ++
.../rendering/control/ChooseWidgetTest.java | 61 ++
.../rendering/control/EmbedWidgetTest.java | 467 +++++++++++
.../control/EmbeddedRespondExtractorTest.java | 140 ++++
.../rendering/control/HeaderWidgetTest.java | 65 ++
.../rendering/control/RepeatWidgetTest.java | 117 +++
.../rendering/control/RequireWidgetTest.java | 51 ++
.../rendering/control/ShowIfWidgetTest.java | 46 ++
.../control/TextFieldWidgetTest.java | 37 +
.../rendering/control/TextWidgetTest.java | 110 +++
.../rendering/control/WidgetRegistryTest.java | 38 +
.../MimeTypesRegexIntegrationTest.java | 31 +
.../resource/ResourcesServiceTest.java | 0
.../sitebricks/routing/PageBookImplTest.java | 732 ++++++++++++++++
.../sitebricks/routing/PathMatcherTest.java | 149 ++++
.../routing/WidgetRoutingDispatcherTest.java | 456 ++++++++++
.../test/ContentNegotiationExample.java | 28 +
.../com/google/sitebricks/test/Search.html | 50 ++
.../com/google/sitebricks/test/Search.java | 49 ++
.../java/com/google/sitebricks/test/Wiki.html | 32 +
.../java/com/google/sitebricks/test/Wiki.java | 35 +
.../google/sitebricks/util/TextToolsTest.java | 56 ++
.../resources/com/google/sitebricks/My.xml | 6 +
.../com/google/sitebricks/MyHtml.html | 6 +
.../com/google/sitebricks/MyXhtml.xhtml | 6 +
.../sitebricks/rendering/resource/my.xml | 3 +
slf4j/pom.xml | 58 ++
.../slf4j/Slf4jInjectionTypeListener.java | 55 ++
.../google/sitebricks/slf4j/Slf4jModule.java | 18 +
.../slf4j/Slf4jIntegrationTest.java | 55 ++
stat/pom.xml | 89 ++
.../stat/MemberAnnotatedWithAtStat.java | 69 ++
.../java/com/google/sitebricks/stat/Stat.java | 28 +
.../stat/StatAnnotatedTypeListener.java | 32 +
.../google/sitebricks/stat/StatCollector.java | 90 ++
.../sitebricks/stat/StatDescriptor.java | 77 ++
.../google/sitebricks/stat/StatExposer.java | 36 +
.../google/sitebricks/stat/StatExposers.java | 68 ++
.../google/sitebricks/stat/StatModule.java | 195 +++++
.../google/sitebricks/stat/StatReader.java | 54 ++
.../google/sitebricks/stat/StatReaders.java | 189 +++++
.../google/sitebricks/stat/StatRegistrar.java | 97 +++
.../com/google/sitebricks/stat/Stats.java | 103 +++
.../sitebricks/stat/StatsPublisher.java | 44 +
.../sitebricks/stat/StatsPublishers.java | 94 +++
.../google/sitebricks/stat/StatsServlet.java | 58 ++
.../sitebricks/stat/StatExposersTest.java | 114 +++
.../sitebricks/stat/StatReadersTest.java | 143 ++++
.../sitebricks/stat/StatsIntegrationTest.java | 234 ++++++
.../sitebricks/stat/StatsPublishersTest.java | 105 +++
.../stat/testservices/ChildDummyService.java | 45 +
.../stat/testservices/DummyService.java | 63 ++
.../StatExposerTestingService.java | 98 +++
.../stat/testservices/StaticDummyService.java | 47 ++
434 files changed, 31928 insertions(+)
create mode 100644 sitebricks-acceptance-tests/pom.xml
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Case.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/CompileErrors.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/ContentNegotiation.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Conversion.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/DecoratedPage.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/DecoratorPage.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/DynamicJs.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Embed.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Forms.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/HelloWorld.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/HelloWorldService.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/HiddenFieldMethod.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/I18n.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/MvelTemplateExample.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/NextPage.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/PageChain.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/PostableRestfulWebService.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Repeat.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebService.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceNoAnnotations.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithCRUD.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithCRUDConversions.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithMatrixParams.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithSubpaths.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithSubpaths2.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/SelectRouting.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/ShowIf.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/SitebricksConfig.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Start.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/StartAware.java
create mode 100644 sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/TestPage.java
create mode 100644 sitebricks-acceptance-tests/src/main/resources/Case.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/CompileErrors.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/ContentNegotiation.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/Conversion.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/DecoratedPage.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/Decorator.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/Embed.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/Forms.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/HelloWorld.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/HiddenFieldMethod.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/I18n.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/MvelTemplateExample.mvel
create mode 100644 sitebricks-acceptance-tests/src/main/resources/NextPage.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/PageChain.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/Repeat.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/SelectRouting.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/ShowIf.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/TestPage.html
create mode 100644 sitebricks-acceptance-tests/src/main/resources/WEB-INF/web.xml
create mode 100644 sitebricks-acceptance-tests/src/main/resources/default.css
create mode 100644 sitebricks-acceptance-tests/src/main/resources/dynamic.js
create mode 100644 sitebricks-acceptance-tests/src/main/resources/index.html
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/ServletContainerIntegrationTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/CaseAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/ConnegAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/ConversionAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/DecoratorAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/DynamicJsAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/EmbedAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/FormsAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/HelloWorldAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/HiddenFieldMethodAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/I18nAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/JettyAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/PageChainingAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/PostableRestfuWebServiceAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RepeatAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithCRUDAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithCRUDConversionsAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithMatrixParamsAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithSubpaths2AcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithSubpathsAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/SelectRoutingAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/SpiRestfuWebServiceWithSubpaths2AcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/StatsAcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/CasePage.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/ConnegPage.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/ConversionPage.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/DecoratorPage.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/DynamicJsPage.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/EmbedPage.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/FormsPage.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/HelloWorldPage.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/HiddenFieldMethodPage.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/I18nPage.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/PageChainPage.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/RepeatPage.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/SelectRoutingPage.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/StatsPage.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/util/AcceptanceTest.java
create mode 100644 sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/util/Jetty.java
create mode 100644 sitebricks-client/pom.xml
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/AHCWebClient.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/CommonsWeb.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/Transport.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/TransportException.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/Web.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/WebClient.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/WebClientBuilder.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/WebResponse.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/WebResponseImpl.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/transport/ByteArrayTransport.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/transport/JacksonJsonTransport.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Json.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Raw.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/transport/SimpleTextTransport.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Text.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/transport/XStreamXmlTransport.java
create mode 100644 sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Xml.java
create mode 100644 sitebricks-client/src/test/java/com/google/sitebricks/client/WebClientEdslIntegrationTest.java
create mode 100644 sitebricks-client/src/test/java/com/google/sitebricks/client/WebClientIntegrationTest.java
create mode 100644 sitebricks-client/src/test/java/com/google/sitebricks/client/transport/RawTransportTest.java
create mode 100644 sitebricks-client/src/test/java/com/google/sitebricks/client/transport/SimpleTextTransportTest.java
create mode 100644 sitebricks-client/src/test/java/com/google/sitebricks/client/transport/XmlTransportTest.java
create mode 100644 sitebricks-client/src/test/resources/com/google/sitebricks/client/tweet.json
create mode 100644 sitebricks-converter/pom.xml
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/Converter.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ConverterAdaptor.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ConverterRegistry.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ConverterUtils.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/DateConverters.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/DummyTypeConverter.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/MvelConversionHandlers.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/MvelTypeConverter.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/NumberConverters.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ObjectToStringConverter.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/SingletonListConverter.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/StandardTypeConverter.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/StringToPrimitiveConverters.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/TypeConverter.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/CaptureType.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/CaptureTypeImpl.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/GenericArrayTypeImpl.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/Generics.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/ParameterizedTypeImpl.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/TypeToken.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/VarMap.java
create mode 100644 sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/WildcardTypeImpl.java
create mode 100644 sitebricks-converter/src/test/java/com/google/sitebricks/conversion/MvelTypeConverterTest.java
create mode 100644 sitebricks-converter/src/test/java/com/google/sitebricks/conversion/StandardTypeConverterTest.java
create mode 100644 sitebricks-converter/src/test/java/com/google/sitebricks/conversion/TestTypeConverter.java
create mode 100644 sitebricks-jetty-archetype/pom.xml
create mode 100644 sitebricks-jetty-archetype/src/main/java/info/sitebricks/example/AppConfig.java
create mode 100644 sitebricks-jetty-archetype/src/main/java/info/sitebricks/example/Main.java
create mode 100644 sitebricks-jetty-archetype/src/main/java/info/sitebricks/example/web/HomePage.java
create mode 100644 sitebricks-jetty-archetype/src/main/resources/HomePage.html
create mode 100644 sitebricks-jetty-archetype/src/main/resources/WEB-INF/web.xml
create mode 100644 sitebricks-mail/pom.xml
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/CommandCompletion.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/FolderObserver.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/Mail.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClient.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClientConfig.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClientHandler.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClientPipelineFactory.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/NettyImapClient.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/SitebricksMail.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/example
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Command.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Extractor.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Flag.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Folder.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/FolderExtractor.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/FolderStatus.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/FolderStatusExtractor.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/ListFoldersExtractor.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Message.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/MessageExtractor.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/MessageStatus.java
create mode 100644 sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/MessageStatusExtractor.java
create mode 100644 sitebricks-mail/src/test/java/com/google/sitebricks/mail/MailClientIntegrationTest.java
create mode 100644 sitebricks-mail/src/test/java/com/google/sitebricks/mail/imap/MessageStatusExtractorTest.java
create mode 100644 sitebricks-mail/src/test/java/com/google/sitebricks/mail/test/integration/TestImap.java
create mode 100644 sitebricks-mail/src/test/java/com/google/sitebricks/mail/webapp/Home.java
create mode 100644 sitebricks-mail/src/test/java/com/google/sitebricks/mail/webapp/WebConfig.java
create mode 100644 sitebricks-mail/src/test/resources/Home.html
create mode 100644 sitebricks-mail/src/test/resources/WEB-INF/web.xml
create mode 100644 sitebricks-mail/src/test/resources/com/google/sitebricks/mail/imap/fetch_all_data.txt
create mode 100644 sitebricks-options/pom.xml
create mode 100644 sitebricks-options/src/main/java/com/google/sitebricks/options/Options.java
create mode 100644 sitebricks-options/src/main/java/com/google/sitebricks/options/OptionsModule.java
create mode 100644 sitebricks-options/src/test/java/com/google/sitebricks/options/OptionsTest.java
create mode 100644 sitebricks-options/src/test/resources/com/google/sitebricks/options/options.properties
create mode 100644 sitebricks-web/pom.xml
create mode 100644 sitebricks-web/src/main/java/info/sitebricks/Jetty.java
create mode 100644 sitebricks-web/src/main/java/info/sitebricks/SitebricksConfig.java
create mode 100644 sitebricks-web/src/main/java/info/sitebricks/data/Document.java
create mode 100644 sitebricks-web/src/main/java/info/sitebricks/data/Index.java
create mode 100644 sitebricks-web/src/main/java/info/sitebricks/persist/PersistAware.java
create mode 100644 sitebricks-web/src/main/java/info/sitebricks/persist/PersistFilter.java
create mode 100644 sitebricks-web/src/main/java/info/sitebricks/persist/StoreModule.java
create mode 100644 sitebricks-web/src/main/java/info/sitebricks/persist/WikiStore.java
create mode 100644 sitebricks-web/src/main/java/info/sitebricks/web/Home.java
create mode 100644 sitebricks-web/src/main/java/info/sitebricks/web/WikiService.java
create mode 100644 sitebricks-web/src/main/resources/Home.html
create mode 100644 sitebricks-web/src/main/resources/WEB-INF/web.xml
create mode 100644 sitebricks-web/src/main/resources/info/sitebricks/data/Document.html
create mode 100644 sitebricks-web/src/main/resources/js/jquery-1.4.3.min.js
create mode 100644 sitebricks-web/src/main/resources/js/json2.js
create mode 100644 sitebricks-web/src/main/resources/js/rpc.js
create mode 100644 sitebricks-web/src/main/resources/js/sitebricks.js
create mode 100644 sitebricks-web/src/main/resources/main.css
create mode 100644 sitebricks/TODO
create mode 100644 sitebricks/pom.atom
create mode 100644 sitebricks/pom.xml
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/ActionDescriptor.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/At.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/Aware.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/AwareModule.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/Bootstrapper.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/Bricks.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/Classes.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/DebugModePageBook.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/DebugModeRoutingDispatcher.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/Evaluator.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/Export.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/GaeModule.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/HiddenMethodFilter.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/Localizer.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/MissingTemplateException.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/MvelEvaluator.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/NoSuchResourceException.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/PackageScanFailedException.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/PageBinder.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/Renderable.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/Respond.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/ScanAndCompileBootstrapper.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/Show.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/Shutdowner.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/SitebricksFilter.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/SitebricksInternalModule.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/SitebricksModule.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/SitebricksServletModule.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/StringBuilderRespond.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/Template.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/TemplateLoader.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/TemplateLoadingException.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/Visible.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/binding/ConcurrentPropertyCache.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/binding/CookieBasedFlashCache.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/binding/FlashCache.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/binding/GaeFlashCache.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/binding/HttpSessionFlashCache.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/binding/InvalidBindingException.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/binding/MvelRequestBinder.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/binding/NoFlashCache.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/binding/PropertyCache.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/binding/RequestBinder.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/AnalysisError.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/AnalysisErrors.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/AnnotationNode.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/AnnotationParser.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/CompileError.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/CompileErrors.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/CompiledToken.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/Compilers.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/Dom.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/EvaluatorCompiler.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/ExpressionCompileException.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/FlatTemplateCompiler.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/HtmlParser.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/HtmlTemplateCompiler.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/MvelEvaluatorCompiler.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/Parsing.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/RepeatToken.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/RequireWidgetInternPool.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/StandardCompilers.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/TemplateCompileException.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/TemplateParseException.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/Token.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/XmlTemplateCompiler.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/template/MvelTemplateCompiler.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/compiler/template/freemarker/FreemarkerTemplateCompiler.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/core/CaseWidget.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/core/Repeat.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/core/ShowIf.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/core/package-info.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/debug/DebugPage.html
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/debug/DebugPage.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/headless/HeadlessRenderer.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/headless/Reply.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/headless/ReplyBasedHeadlessRenderer.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/headless/ReplyMaker.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/headless/Request.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/headless/Service.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/http/Delete.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/http/Get.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/http/Head.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/http/Post.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/http/Put.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/http/Select.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/http/Trace.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/http/negotiate/Accept.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/http/negotiate/ConnegModule.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/http/negotiate/ContentNegotiator.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/http/negotiate/ExactMatchNegotiator.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/http/negotiate/Negotiation.java
create mode 100755 sitebricks/src/main/java/com/google/sitebricks/http/negotiate/RegexNegotiator.java
create mode 100755 sitebricks/src/main/java/com/google/sitebricks/http/negotiate/WildcardNegotiator.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/i18n/Message.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/Attributes.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/Decorated.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/EmbedAs.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/SelfRendering.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/Strings.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/Templates.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/With.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/ArgumentWidget.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/Chains.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/ChooseWidget.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/DecorateWidget.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/DefaultWidgetRegistry.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/EmbedWidget.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/EmbeddedRespond.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/EmbeddedRespondFactory.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/HeaderWidget.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/IncludeWidget.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/NoSuchWidgetException.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/ProceedingWidgetChain.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/RawTextWidget.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/RepeatWidget.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/RequireWidget.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/ShowIfWidget.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/SingletonWidgetChain.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/TerminalWidgetChain.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/TextFieldWidget.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/TextWidget.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/WidgetChain.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/WidgetRegistry.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/WidgetWrapper.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/XmlDirectiveWidget.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/control/XmlWidget.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/resource/Assets.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/resource/ClasspathResourcesService.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/resource/ResourceLoadingException.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/rendering/resource/ResourcesService.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/routing/Action.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/routing/DefaultPageBook.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/routing/EventDispatchException.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/routing/InMemorySystemMetrics.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/routing/InvalidEventHandlerException.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/routing/PageBook.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/routing/PathMatcher.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/routing/PathMatcherChain.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/routing/Production.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/routing/RoutingDispatcher.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/routing/ServiceAction.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/routing/SpiAction.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/routing/SystemMetrics.java
create mode 100644 sitebricks/src/main/java/com/google/sitebricks/routing/WidgetRoutingDispatcher.java
create mode 100644 sitebricks/src/main/resources/com/google/sitebricks/core/CaseWidget.html
create mode 100644 sitebricks/src/main/resources/com/google/sitebricks/core/ll.gif
create mode 100644 sitebricks/src/main/resources/com/google/sitebricks/core/lr.gif
create mode 100644 sitebricks/src/main/resources/com/google/sitebricks/core/ul.gif
create mode 100644 sitebricks/src/main/resources/com/google/sitebricks/core/ur.gif
create mode 100644 sitebricks/src/main/resources/com/google/sitebricks/rendering/resource/mimetypes.properties
create mode 100644 sitebricks/src/main/resources/com/google/sitebricks/templates.properties
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/EdslTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/LocalizationTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/RespondTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/RespondersForTesting.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/TemplateLoaderTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/WidgetFilterTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/binding/MvelRequestBinderTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/binding/PropertyCacheTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/compiler/FreemarkerTemplateCompilerTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/compiler/HtmlTemplateCompilerTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/compiler/XmlLineNumberParsingTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/compiler/XmlTemplateCompilerTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/headless/HeadlessReplyTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/headless/ReplyEdslTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/http/negotiate/ExactMatchNegotiatorTest.java
create mode 100755 sitebricks/src/test/java/com/google/sitebricks/http/negotiate/RegexNegotiatorTest.java
create mode 100755 sitebricks/src/test/java/com/google/sitebricks/http/negotiate/WildcardNegotiatorTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/DynTypedMvelEvaluatorCompiler.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/EvaluatorCompilerTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/MvelGenericsConfidenceTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/ParsingTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/control/ChooseWidgetTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/control/EmbedWidgetTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/control/EmbeddedRespondExtractorTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/control/HeaderWidgetTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/control/RepeatWidgetTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/control/RequireWidgetTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/control/ShowIfWidgetTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/control/TextFieldWidgetTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/control/TextWidgetTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/control/WidgetRegistryTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/resource/MimeTypesRegexIntegrationTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/rendering/resource/ResourcesServiceTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/routing/PageBookImplTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/routing/PathMatcherTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/routing/WidgetRoutingDispatcherTest.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/test/ContentNegotiationExample.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/test/Search.html
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/test/Search.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/test/Wiki.html
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/test/Wiki.java
create mode 100644 sitebricks/src/test/java/com/google/sitebricks/util/TextToolsTest.java
create mode 100644 sitebricks/src/test/resources/com/google/sitebricks/My.xml
create mode 100644 sitebricks/src/test/resources/com/google/sitebricks/MyHtml.html
create mode 100644 sitebricks/src/test/resources/com/google/sitebricks/MyXhtml.xhtml
create mode 100644 sitebricks/src/test/resources/com/google/sitebricks/rendering/resource/my.xml
create mode 100644 slf4j/pom.xml
create mode 100644 slf4j/src/main/java/com/google/sitebricks/slf4j/Slf4jInjectionTypeListener.java
create mode 100644 slf4j/src/main/java/com/google/sitebricks/slf4j/Slf4jModule.java
create mode 100644 slf4j/src/test/java/com/google/sitebricks/slf4j/Slf4jIntegrationTest.java
create mode 100644 stat/pom.xml
create mode 100644 stat/src/main/java/com/google/sitebricks/stat/MemberAnnotatedWithAtStat.java
create mode 100644 stat/src/main/java/com/google/sitebricks/stat/Stat.java
create mode 100644 stat/src/main/java/com/google/sitebricks/stat/StatAnnotatedTypeListener.java
create mode 100644 stat/src/main/java/com/google/sitebricks/stat/StatCollector.java
create mode 100644 stat/src/main/java/com/google/sitebricks/stat/StatDescriptor.java
create mode 100644 stat/src/main/java/com/google/sitebricks/stat/StatExposer.java
create mode 100644 stat/src/main/java/com/google/sitebricks/stat/StatExposers.java
create mode 100644 stat/src/main/java/com/google/sitebricks/stat/StatModule.java
create mode 100644 stat/src/main/java/com/google/sitebricks/stat/StatReader.java
create mode 100644 stat/src/main/java/com/google/sitebricks/stat/StatReaders.java
create mode 100644 stat/src/main/java/com/google/sitebricks/stat/StatRegistrar.java
create mode 100644 stat/src/main/java/com/google/sitebricks/stat/Stats.java
create mode 100644 stat/src/main/java/com/google/sitebricks/stat/StatsPublisher.java
create mode 100644 stat/src/main/java/com/google/sitebricks/stat/StatsPublishers.java
create mode 100644 stat/src/main/java/com/google/sitebricks/stat/StatsServlet.java
create mode 100644 stat/src/test/java/com/google/sitebricks/stat/StatExposersTest.java
create mode 100644 stat/src/test/java/com/google/sitebricks/stat/StatReadersTest.java
create mode 100644 stat/src/test/java/com/google/sitebricks/stat/StatsIntegrationTest.java
create mode 100644 stat/src/test/java/com/google/sitebricks/stat/StatsPublishersTest.java
create mode 100644 stat/src/test/java/com/google/sitebricks/stat/testservices/ChildDummyService.java
create mode 100644 stat/src/test/java/com/google/sitebricks/stat/testservices/DummyService.java
create mode 100644 stat/src/test/java/com/google/sitebricks/stat/testservices/StatExposerTestingService.java
create mode 100644 stat/src/test/java/com/google/sitebricks/stat/testservices/StaticDummyService.java
diff --git a/sitebricks-acceptance-tests/pom.xml b/sitebricks-acceptance-tests/pom.xml
new file mode 100644
index 00000000..ed24efda
--- /dev/null
+++ b/sitebricks-acceptance-tests/pom.xml
@@ -0,0 +1,137 @@
+
+
+ 4.0.0
+
+ com.google.sitebricks
+ sitebricks-parent
+ 0.8.5
+
+ sitebricks-acceptance-tests
+ Sitebricks :: Acceptance Tests
+
+
+
+ openqa-releases
+ OpenQA Releases
+ http://nexus.openqa.org/content/repositories/releases
+
+ true
+
+
+ false
+
+
+
+ codehaus
+ Codehaus Releases
+ http://repository.codehaus.org
+
+ true
+
+
+ false
+
+
+
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.6.1
+
+
+
+
+
+
+ org.mortbay.jetty
+ jetty
+
+
+ org.mortbay.jetty
+ jetty-util
+
+
+ org.mortbay.jetty
+ servlet-api-2.5
+
+
+ org.seleniumhq.webdriver
+ webdriver-common
+ test
+
+
+ org.seleniumhq.webdriver
+ webdriver-support
+ test
+
+
+ org.seleniumhq.webdriver
+ webdriver-htmlunit
+ test
+
+
+ commons-collections
+ commons-collections
+
+
+ com.google.inject.extensions
+ guice-servlet
+
+
+ com.google.sitebricks
+ sitebricks
+
+
+ com.google.sitebricks
+ sitebricks-stat
+ ${project.version}
+
+
+ org.testng
+ testng
+ jdk15
+
+
+
+
+
+
+ src/main/resources
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 1.6
+ 1.6
+
+
+
+ org.apache.maven.plugins
+ maven-release-plugin
+ 2.0-beta-9
+
+ https://google-sitebricks.googlecode.com/svn/tags
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 2.3.1
+
+
+
+ test-jar
+
+
+
+
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Case.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Case.java
new file mode 100644
index 00000000..0d44689b
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Case.java
@@ -0,0 +1,19 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.At;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail com)
+ */
+@At("/case")
+public class Case {
+ private String color = "green";
+
+ public String getColor() {
+ return color;
+ }
+
+ public void setColor(String color) {
+ this.color = color;
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/CompileErrors.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/CompileErrors.java
new file mode 100644
index 00000000..f080e35e
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/CompileErrors.java
@@ -0,0 +1,21 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.At;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail com)
+ */
+@At("/error")
+public class CompileErrors {
+ public String getMessage1() {
+ return "";
+ }
+
+ public int getMessage2() {
+ return 0;
+ }
+
+ public String getMessage3() {
+ throw new RuntimeException("an exception");
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/ContentNegotiation.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/ContentNegotiation.java
new file mode 100644
index 00000000..40b13da2
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/ContentNegotiation.java
@@ -0,0 +1,29 @@
+package com.google.sitebricks.example;
+
+
+import com.google.sitebricks.At;
+import com.google.sitebricks.http.Get;
+import com.google.sitebricks.http.negotiate.Accept;
+
+
+@At("/conneg")
+public class ContentNegotiation {
+
+ // By default we serve gif, hehehehe.
+ private String content = "GIF";
+
+ @Get @Accept("image/jpeg")
+ public void jpeg() {
+ content = "JPEG";
+ }
+
+
+ @Get @Accept("image/png")
+ public void png() {
+ content = "PNG";
+ }
+
+ public String getContent() {
+ return content;
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Conversion.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Conversion.java
new file mode 100644
index 00000000..0f4bcfe0
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Conversion.java
@@ -0,0 +1,48 @@
+package com.google.sitebricks.example;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import com.google.sitebricks.At;
+
+@At("/conversion")
+public class Conversion {
+ private Date date;
+ private Calendar calendar;
+ private String message;
+ private Double dbl;
+
+ public Date getDate() {
+ return date;
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public Calendar getCalendar() {
+ return calendar;
+ }
+ public void setCalendar(Calendar calendar) {
+ this.calendar = calendar;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public Double getDbl() {
+ return dbl;
+ }
+
+ public void setDbl(Double dbl) {
+ this.dbl = dbl;
+ }
+
+
+
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/DecoratedPage.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/DecoratedPage.java
new file mode 100644
index 00000000..d531f5bc
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/DecoratedPage.java
@@ -0,0 +1,16 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.rendering.Decorated;
+
+@Decorated
+public class DecoratedPage extends DecoratorPage {
+
+ @Override
+ public String getWorld() {
+ return "This comes from the subclass";
+ }
+
+ public String getDescription() {
+ return "very cool";
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/DecoratorPage.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/DecoratorPage.java
new file mode 100644
index 00000000..3427484c
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/DecoratorPage.java
@@ -0,0 +1,12 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.Show;
+
+@Show("/Decorator.html")
+public abstract class DecoratorPage {
+ public String getHello() {
+ return "Hello (from the superclass)";
+ }
+
+ public abstract String getWorld();
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/DynamicJs.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/DynamicJs.java
new file mode 100644
index 00000000..a50c1ade
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/DynamicJs.java
@@ -0,0 +1,17 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.At;
+import com.google.sitebricks.Show;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail com)
+ */
+@At("/dynamic.js") @Show("dynamic.js")
+public class DynamicJs {
+ public static final String A_MESSAGE = "Hi from google-sitebricks! (this message"
+ + " was dynamically generated =)";
+
+ public String getMessage() {
+ return A_MESSAGE;
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Embed.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Embed.java
new file mode 100644
index 00000000..994129db
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Embed.java
@@ -0,0 +1,23 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.At;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@At("/embed")
+public class Embed {
+
+ private List arg = Arrays.asList(
+ "Embedding in google-sitebricks is awesome!",
+ "Embedding in google-sitebricks totally rules!",
+ "google-sitebricks embed FTW!"
+ );
+
+ public List getArg() {
+ return arg;
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Forms.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Forms.java
new file mode 100644
index 00000000..7e09ceb8
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Forms.java
@@ -0,0 +1,40 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.At;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@At("/forms")
+public class Forms {
+ private String text = "initial textfield value";
+ private String chosen = "(nothing)";
+ private List autobots = Arrays.asList("Bumblebee", "Ultra Magnus", "Optimus Prime", "Kup", "Hot Rod");
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public List getAutobots() {
+ return autobots;
+ }
+
+ public void setAutobots(List autobots) {
+ this.autobots = autobots;
+ }
+
+ public String getChosen() {
+ return chosen;
+ }
+
+ public void setChosen(String chosen) {
+ this.chosen = chosen;
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/HelloWorld.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/HelloWorld.java
new file mode 100644
index 00000000..8f5cc20c
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/HelloWorld.java
@@ -0,0 +1,27 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.At;
+import com.google.sitebricks.rendering.EmbedAs;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@At("/hello") @EmbedAs("Hello")
+public class HelloWorld {
+ public static volatile String HELLO_MSG = "Hello from google-sitebricks!";
+
+ private String message = HELLO_MSG;
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ // Some deterministic mangled representation of the input.
+ public String mangle(String s) {
+ return "" + s.hashCode();
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/HelloWorldService.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/HelloWorldService.java
new file mode 100644
index 00000000..08657a07
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/HelloWorldService.java
@@ -0,0 +1,48 @@
+package com.google.sitebricks.example;
+
+import com.google.inject.Inject;
+import com.google.sitebricks.At;
+import com.google.sitebricks.Show;
+import com.google.sitebricks.headless.Reply;
+import com.google.sitebricks.headless.Service;
+import com.google.sitebricks.http.Get;
+import com.google.sitebricks.rendering.Templates;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Show("HelloWorld.html") @Service
+public class HelloWorldService {
+ public static final String HELLO_MSG = "Hello from google-sitebricks!";
+
+ private final Templates templates;
+ private String message = HELLO_MSG;
+
+ @Inject
+ public HelloWorldService(Templates templates) {
+ this.templates = templates;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ // Some deterministic mangled representation of the input.
+ public String mangle(String s) {
+ return "" + s.hashCode();
+ }
+
+ @Get
+ public Reply> get() {
+ return Reply.with(this).template(HelloWorldService.class);
+ }
+
+ @At("/direct") @Get
+ public Reply> getDirect() {
+ return Reply.with(templates.render(HelloWorldService.class, this));
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/HiddenFieldMethod.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/HiddenFieldMethod.java
new file mode 100644
index 00000000..bd5fb6f7
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/HiddenFieldMethod.java
@@ -0,0 +1,43 @@
+package com.google.sitebricks.example;
+
+
+import com.google.sitebricks.At;
+import com.google.sitebricks.http.Post;
+import com.google.sitebricks.http.Put;
+
+
+/**
+ * @author Peter Knego
+ */
+@At("/hiddenfieldmethod")
+public class HiddenFieldMethod {
+ private String text = "initial textfield value";
+
+ private String putMessage = "";
+
+ public String getPutMessage() {
+ return putMessage;
+ }
+
+ public void setPutMessage(String putMessage) {
+ this.putMessage = putMessage;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ @Put
+ public void put() {
+ putMessage = "Submitted via PUT";
+ }
+
+ @Post
+ public void post() {
+ putMessage = "Submitted via POST";
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/I18n.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/I18n.java
new file mode 100644
index 00000000..c35f54a4
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/I18n.java
@@ -0,0 +1,67 @@
+package com.google.sitebricks.example;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import com.google.sitebricks.At;
+import com.google.sitebricks.i18n.Message;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Locale;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@At("/i18n")
+public class I18n {
+ // These would typically be provided by a translated set resource bundle (external).
+ public static final String HELLO = "hello";
+ public static final String HELLO_IN_FRENCH = "Bonjour misieu ${person}!";
+
+
+ private final MyMessages messages;
+ private final HttpServletRequest request;
+
+ private String name;
+
+ @Inject
+ public I18n(HttpServletRequest request, MyMessages messages) {
+ this.messages = messages;
+ this.request = request;
+ }
+
+
+ public String getMessage() {
+ // only evaluate our message if the user has entered her name.
+ return null == name ? "" : messages.hello(name);
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Locale getLocale() {
+ return request.getLocale();
+ }
+
+ /**
+ * This is the i18N message interface. By default we use the messages provided
+ * in the annotation. You can customize these by using the localize() rule in
+ * your sitebricks module with different resource bundles and locales.
+ *
+ * Note that these messages can be given arguments and even exposed directly to
+ * your template if you so wish, you have to invoke the method with parens like
+ * so:
+ *
+ *
+ *
+ * ${messages.hello("Friend")}
+ *
+ * Will invoke the hello() method below with a string argument "Friend". At runtime
+ * this will produce localized output in whatever locale the browser requests and
+ * is available in your translation sets.
+ */
+ public static interface MyMessages {
+ @Message(message = "Hello there ${person}!")
+ String hello(@Named("person") String name);
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/MvelTemplateExample.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/MvelTemplateExample.java
new file mode 100644
index 00000000..261a841c
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/MvelTemplateExample.java
@@ -0,0 +1,17 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.At;
+import com.google.sitebricks.Show;
+
+/**
+ * Example of a page rendered with a different templating technology
+ * (other than sitebricks).
+ */
+@At("/template/mvel") @Show("MvelTemplateExample.mvel")
+public class MvelTemplateExample {
+ private final String message = "hey hey! Generated by MVEL templates dynamically =)";
+
+ public String getMessage() {
+ return message;
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/NextPage.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/NextPage.java
new file mode 100644
index 00000000..735bf64b
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/NextPage.java
@@ -0,0 +1,21 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.At;
+
+/**
+ * The target page that receives the state.
+ *
+ * @author dhanji@google.com (Dhanji R. Prasanna)
+ */
+@At("/nextpage")
+public class NextPage {
+ private String persistedValue;
+
+ public NextPage(String persistedValue) {
+ this.persistedValue = persistedValue;
+ }
+
+ public String getPersistedValue() {
+ return persistedValue;
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/PageChain.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/PageChain.java
new file mode 100644
index 00000000..aa8d952b
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/PageChain.java
@@ -0,0 +1,26 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.At;
+import com.google.sitebricks.http.Post;
+
+/**
+ * Demonstrates passing state between pages, without
+ * leaking it to the client or using a persistent datastore.
+ *
+ * @author dhanji@google.com (Dhanji R. Prasanna)
+ */
+@At("/pagechain")
+public class PageChain {
+ private String userValue;
+
+ @Post NextPage redirect() {
+
+ // Redirect to nextpage and use this provided instance,
+ // that way we pass the custom value thru.
+ return new NextPage(userValue);
+ }
+
+ public void setUserValue(String userValue) {
+ this.userValue = userValue;
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/PostableRestfulWebService.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/PostableRestfulWebService.java
new file mode 100644
index 00000000..ae94f6a8
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/PostableRestfulWebService.java
@@ -0,0 +1,40 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.At;
+import com.google.sitebricks.client.transport.Json;
+import com.google.sitebricks.headless.Reply;
+import com.google.sitebricks.headless.Request;
+import com.google.sitebricks.headless.Service;
+import com.google.sitebricks.http.Post;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Lets you send JSON data.
+ *
+ * @author Dhanji R. Prasanna (dhanji@gmail com)
+ */
+@At("/postable") @Service
+public class PostableRestfulWebService {
+
+ @Post
+ public Reply postBook(HttpServletRequest servletRequest, Request request) {
+ RestfulWebService.Book perdido = request.read(RestfulWebService.Book.class).as(Json.class);
+
+ boolean assertions = RestfulWebService.PERDIDO_STREET_STATION.equals(perdido.getName())
+ && RestfulWebService.CHINA_MIEVILLE.equals(perdido.getAuthor())
+ && RestfulWebService.PAGE_COUNT == perdido.getPageCount();
+
+ // Assert the request params are legit.
+ @SuppressWarnings("unchecked")
+ Map map = servletRequest.getParameterMap();
+ for (Map.Entry entry : map.entrySet()) {
+ assertions = assertions
+ && Arrays.asList(entry.getValue()).equals(request.params().get(entry.getKey()));
+ }
+
+ return assertions ? Reply.with(perdido.getAuthor()) : Reply.with("failed");
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Repeat.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Repeat.java
new file mode 100644
index 00000000..e2e51632
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Repeat.java
@@ -0,0 +1,31 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.At;
+
+import java.util.*;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@At("/repeat")
+public class Repeat {
+ private static final List NAMES = Arrays.asList("Dhanji", "Josh", "Jody", "Iron Man");
+
+ //property returns a list of names
+ public List getNames() {
+ return NAMES;
+ }
+
+ //try a set this time, returns movies (to demo nested repeat)
+ public Set getMovies() {
+ return new HashSet(Arrays.asList(new Movie(), new Movie(), new Movie()));
+ }
+
+ public static class Movie {
+
+ //try a collection this time. same as property Repeat.getNames() from the outer class
+ public Collection getActors() {
+ return NAMES;
+ }
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebService.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebService.java
new file mode 100644
index 00000000..0807adca
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebService.java
@@ -0,0 +1,83 @@
+package com.google.sitebricks.example;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Injector;
+import com.google.sitebricks.At;
+import com.google.sitebricks.client.transport.Json;
+import com.google.sitebricks.headless.Reply;
+import com.google.sitebricks.headless.Service;
+import com.google.sitebricks.http.Get;
+import com.google.sitebricks.http.Post;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail com)
+ */
+@At("/service") @Service
+public class RestfulWebService {
+ public static final String PERDIDO_STREET_STATION = "Perdido Street Station";
+ public static final String CHINA_MIEVILLE = "China Mieville";
+ public static final int PAGE_COUNT = 789;
+
+ @Get
+ public Reply books(Injector injector, HttpServletRequest request,
+ @SitebricksConfig.Test Start start) {
+ Preconditions.checkNotNull(injector, "method argument injection failed");
+ Preconditions.checkNotNull(request, "method argument injection failed");
+ Preconditions.checkNotNull(start, "method argument injection failed");
+
+ Book perdido = new Book();
+ perdido.setAuthor(CHINA_MIEVILLE);
+ perdido.setName(PERDIDO_STREET_STATION);
+ perdido.setPageCount(PAGE_COUNT);
+
+ return Reply.with(perdido)
+ .headers(ImmutableMap.of("hi", "there"))
+ .type("application/json")
+ .as(Json.class);
+ }
+
+ @Post
+ public Reply> redirect() {
+ return Reply.saying()
+ .redirect("/other");
+ }
+
+
+ /**
+ * A data model object, or "Entity" that we will use to
+ * generate the reply. This can be any Java object and
+ * typically will not be an inner class like this one.
+ */
+ public static class Book {
+ private String name;
+ private String author;
+ private int pageCount;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ public int getPageCount() {
+ return pageCount;
+ }
+
+ public void setPageCount(int pageCount) {
+ this.pageCount = pageCount;
+ }
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceNoAnnotations.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceNoAnnotations.java
new file mode 100644
index 00000000..22ec63c7
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceNoAnnotations.java
@@ -0,0 +1,40 @@
+package com.google.sitebricks.example;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.sitebricks.client.transport.Json;
+import com.google.sitebricks.headless.Reply;
+import com.google.sitebricks.http.Get;
+import com.google.sitebricks.http.Post;
+
+/**
+ * Used to ensure that the configuration works the same even without
+ * annotations. We have this extra test coz some logic that distinguishes
+ * web services from normal web pages relies on the presence of annotations
+ * (failing explicit module config), and this ensures nothing goes haywire.
+ *
+ * @author Dhanji R. Prasanna (dhanji@gmail com)
+ */
+public class RestfulWebServiceNoAnnotations {
+ public static final String PERDIDO_STREET_STATION = "Perdido Street Station";
+ public static final String CHINA_MIEVILLE = "China Mieville";
+ public static final int PAGE_COUNT = 789;
+
+ @Get
+ public Reply books() {
+ RestfulWebService.Book perdido = new RestfulWebService.Book();
+ perdido.setAuthor(CHINA_MIEVILLE);
+ perdido.setName(PERDIDO_STREET_STATION);
+ perdido.setPageCount(PAGE_COUNT);
+
+ return Reply.with(perdido)
+ .headers(ImmutableMap.of("hi", "there"))
+ .type("application/json")
+ .as(Json.class);
+ }
+
+ @Post
+ public Reply> redirect() {
+ return Reply.saying()
+ .redirect("/other");
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithCRUD.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithCRUD.java
new file mode 100644
index 00000000..b24e3e44
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithCRUD.java
@@ -0,0 +1,61 @@
+package com.google.sitebricks.example;
+
+import com.google.inject.name.Named;
+import com.google.sitebricks.At;
+import com.google.sitebricks.headless.Reply;
+import com.google.sitebricks.headless.Request;
+import com.google.sitebricks.headless.Service;
+import com.google.sitebricks.http.Delete;
+import com.google.sitebricks.http.Get;
+import com.google.sitebricks.http.Post;
+import com.google.sitebricks.http.Put;
+
+/**
+ * Demonstrates CRUD operations in a restful webservice.
+ *
+ * // ------------------------------
+ * // Method URL Action
+ * // ------------------------------
+ * // POST /user CREATE
+ * // GET /user READ (collection)
+ * // GET /user/1 READ (individual)
+ * // PUT /user/1 UPDATE
+ * // DELETE /user/1 DELETE
+ *
+ * @author Jason van Zyl
+ */
+@At("/json/:type") @Service
+public class RestfulWebServiceWithCRUD {
+ public static final String TYPE = "user";
+ public static final String BASE_SERVICE_PATH = "/json/" + TYPE;
+ public static final String CREATE = "CREATE";
+ public static final String READ_COLLECTION = "READ_COLLECTION";
+ public static final String READ_INDIVIDUAL = "READ_INDIVIDUAL";
+ public static final String UPDATE = "UPDATE";
+ public static final String DELETE = "DELETE";
+
+ @Post
+ public Reply> post( Request request, @Named( "type" ) String type ) {
+ return Reply.with(CREATE);
+ }
+
+ @Get
+ public Reply> get( @Named( "type" ) String type ) {
+ return Reply.with(READ_COLLECTION);
+ }
+
+ @At( "/:id" ) @Get
+ public Reply> get( @Named( "type" ) String type, @Named( "id" ) String id ) {
+ return Reply.with(READ_INDIVIDUAL);
+ }
+
+ @At( "/:id" ) @Put
+ public Reply> put( Request request, @Named( "type" ) String type, @Named( "id" ) String id ) {
+ return Reply.with(UPDATE);
+ }
+
+ @At( "/:id" ) @Delete
+ public Reply> delete( @Named( "type" ) String type, @Named( "id" ) String id ) {
+ return Reply.with(DELETE);
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithCRUDConversions.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithCRUDConversions.java
new file mode 100644
index 00000000..b1c5248f
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithCRUDConversions.java
@@ -0,0 +1,196 @@
+package com.google.sitebricks.example;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import com.google.inject.name.Named;
+import com.google.sitebricks.At;
+import com.google.sitebricks.client.transport.Json;
+import com.google.sitebricks.headless.Reply;
+import com.google.sitebricks.headless.Request;
+import com.google.sitebricks.headless.Service;
+import com.google.sitebricks.http.Delete;
+import com.google.sitebricks.http.Get;
+import com.google.sitebricks.http.Post;
+import com.google.sitebricks.http.Put;
+
+@At(RestfulWebServiceWithCRUDConversions.AT_ME)
+@Service
+public class RestfulWebServiceWithCRUDConversions {
+ public static final String AT_ME = "/jsonConversion";
+ public static List widgets = new ArrayList();
+
+ static {
+ populate();
+ }
+
+ private static void populate() {
+ SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy HH:mm");
+ try {
+ addWidget(new Widget(1, "Widget 1", sdf.parse("01-JAN-1990 12:00"), 1.50));
+ addWidget(new Widget(2, "Widget 2", sdf.parse("02-FEB-2000 18:00"), 21.75));
+ addWidget(new Widget(3, "Widget 3", sdf.parse("03-MAR-2010 23:00"), 19.99));
+ } catch (Exception e) {
+ }
+ }
+
+ @Post
+ public Reply post(Request request) {
+ Widget widget = request.read(Widget.class).as(Json.class);
+ addWidget(widget);
+ return Reply.with(widget).as(Json.class).type("application/json");
+ }
+
+ @Put
+ public Reply> put(Request request) {
+ Widget widget = request.read(Widget.class).as(Json.class);
+ addWidget(widget);
+ return Reply.with(widget).as(Json.class).type("application/json");
+ }
+
+ @Get
+ public Reply> getAll() {
+ return Reply.with(widgets).as(Json.class).type("application/json");
+ }
+
+ @At("/:id")
+ @Get
+ public Reply> get(@Named("id") int id) {
+ Widget widget = findWidget(id);
+ if (widget != null)
+ return Reply.with(widget).as(Json.class).type("application/json");
+ return Reply.saying().error();
+ }
+
+ @At("/:id")
+ @Delete
+ public Reply> delete(@Named("id") int id) {
+ Widget widget = removeWidget(id);
+ if (widget != null)
+ return Reply.with(widget).as(Json.class).type("application/json");
+ return Reply.saying().error();
+ }
+
+ public static Widget removeWidget(Widget widget) {
+ return removeWidget(widget.getId());
+ }
+
+ public static Widget removeWidget(int id) {
+ Widget oldWidget = findWidget(id);
+ if (oldWidget != null)
+ widgets.remove(oldWidget);
+ return oldWidget;
+ }
+
+ public static void addWidget(Widget widget) {
+ Widget oldWidget = findWidget(widget.getId());
+ if (oldWidget != null)
+ widgets.remove(oldWidget);
+ widgets.add(widget);
+ }
+
+ public static Widget findWidget(int id) {
+ for (Widget widget : widgets) {
+ if (widget.getId() == id)
+ return widget;
+ }
+ return null;
+ }
+
+ public static class Widget implements Serializable {
+ int id;
+ String name;
+ Date available;
+ double price;
+
+ public Widget() {
+ }
+
+ public Widget(int id, String name, Date available, double price) {
+ this.id = id;
+ this.name = name;
+ this.available = available;
+ this.price = price;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Date getAvailable() {
+ return available;
+ }
+
+ public void setAvailable(Date available) {
+ this.available = available;
+ }
+
+ public double getPrice() {
+ return price;
+ }
+
+ public void setPrice(double price) {
+ this.price = price;
+ }
+
+ public Widget clone() {
+ return new Widget (id, name, available, price);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((available == null) ? 0 : available.hashCode());
+ result = prime * result + id;
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ long temp;
+ temp = Double.doubleToLongBits(price);
+ result = prime * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Widget other = (Widget) obj;
+ if (available == null) {
+ if (other.available != null)
+ return false;
+ } else if (!available.equals(other.available))
+ return false;
+ if (id != other.id)
+ return false;
+ if (name == null) {
+ if (other.name != null)
+ return false;
+ } else if (!name.equals(other.name))
+ return false;
+ if (Double.doubleToLongBits(price) != Double.doubleToLongBits(other.price))
+ return false;
+ return true;
+ }
+
+
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithMatrixParams.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithMatrixParams.java
new file mode 100644
index 00000000..c82661ad
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithMatrixParams.java
@@ -0,0 +1,31 @@
+package com.google.sitebricks.example;
+
+import com.google.inject.name.Named;
+import com.google.sitebricks.At;
+import com.google.sitebricks.headless.Reply;
+import com.google.sitebricks.headless.Request;
+import com.google.sitebricks.headless.Service;
+import com.google.sitebricks.http.Get;
+import com.google.sitebricks.http.Post;
+
+/**
+ * Demonstrates subpaths in a restful webservice.
+ *
+ * @author Dhanji R. Prasanna (dhanji@gmail com)
+ */
+@At("/matrixpath") @Service
+public class RestfulWebServiceWithMatrixParams {
+ public static final String TOPLEVEL = "toplevel_m";
+ public static final String PATH_1 = "path1";
+
+ @Get
+ public Reply> topLevel() {
+ return Reply.with(TOPLEVEL);
+ }
+
+ @At("/:variable/:id") @Post
+ public Reply variableSecondLevel(@Named("variable") String arg, @Named("id") String id,
+ Request request) {
+ return Reply.with(request.matrix().toString() + "_" + arg + "_" + id);
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithSubpaths.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithSubpaths.java
new file mode 100644
index 00000000..62b804d0
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithSubpaths.java
@@ -0,0 +1,56 @@
+package com.google.sitebricks.example;
+
+import com.google.inject.name.Named;
+import com.google.sitebricks.At;
+import com.google.sitebricks.headless.Reply;
+import com.google.sitebricks.headless.Service;
+import com.google.sitebricks.http.Get;
+import com.google.sitebricks.http.Post;
+
+/**
+ * Demonstrates subpaths in a restful webservice.
+ *
+ * @author Dhanji R. Prasanna (dhanji@gmail com)
+ */
+@At("/superpath") @Service
+public class RestfulWebServiceWithSubpaths {
+ public static final String TOPLEVEL = "toplevel";
+ public static final String PATH_1 = "path1";
+ public static final String PATH_2 = "path2";
+ public static final String PATH_3 = "path3";
+
+ @Get
+ public Reply> topLevel() {
+ return Reply.with(TOPLEVEL);
+ }
+
+ @At("/subpath1") @Post
+ public Reply path1() {
+ return Reply.with(PATH_1);
+ }
+
+ @At("/subpath2") @Post
+ public Reply path2() {
+ return Reply.with(PATH_2);
+ }
+
+ @At("/subpath3") @Post
+ public Reply path3() {
+ return Reply.with(PATH_3);
+ }
+
+ @At("/subpath1/:variable") @Post
+ public Reply variable(@Named("variable") String arg) {
+ return Reply.with(arg);
+ }
+
+ @At("/subpath1/:variable/:id") @Post
+ public Reply variableSecondLevel(@Named("variable") String arg, @Named("id") String id) {
+ return Reply.with(arg + "_" + id);
+ }
+
+ @At("/subpath3/:sec") @Post
+ public Reply variableSubpath2(@Named("sec") String arg) {
+ return Reply.with(arg);
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithSubpaths2.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithSubpaths2.java
new file mode 100644
index 00000000..70307e5b
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/RestfulWebServiceWithSubpaths2.java
@@ -0,0 +1,98 @@
+package com.google.sitebricks.example;
+
+import com.google.inject.name.Named;
+import com.google.sitebricks.At;
+import com.google.sitebricks.headless.Reply;
+import com.google.sitebricks.headless.Service;
+import com.google.sitebricks.http.Delete;
+import com.google.sitebricks.http.Get;
+import com.google.sitebricks.http.Post;
+import com.google.sitebricks.http.Put;
+
+/**
+ * Demonstrates subpaths in a restful webservice.
+ *
+ * @author Dhanji R. Prasanna (dhanji@gmail com)
+ */
+@At("/superpath2/:dynamic") @Service
+public class RestfulWebServiceWithSubpaths2 {
+ public static final String TOPLEVEL = "test1";
+ public static final String PATH_1 = "path1";
+ public static final String PATH_1_PUT = "path1put";
+ public static final String PATH_1_DELETE = "path1delete";
+
+ @Get
+ public Reply> topLevel(@Named("dynamic") String dynamic) {
+ return Reply.with(dynamic);
+ }
+
+ @At("/subpath1") @Post
+ public Reply path1(@Named("dynamic") String dynamic) {
+ return Reply.with(PATH_1);
+ }
+
+ @At("/subpath1") @Put
+ public Reply path1Put(@Named("dynamic") String dynamic) {
+ return Reply.with(PATH_1_PUT);
+ }
+
+ @At("/subpath1") @Delete
+ public Reply path1Delete(@Named("dynamic") String dynamic) {
+ return Reply.with(PATH_1_DELETE);
+ }
+
+ @At("/:second") @Post
+ public Reply path2(@Named("dynamic") String dynamic, @Named("second") String second) {
+ return Reply.with(dynamic + "_" + second);
+ }
+
+ @At("/:second") @Delete
+ public Reply path2Delete(@Named("dynamic") String dynamic,
+ @Named("second") String second) {
+ return Reply.with("delete:" + dynamic + "_" + second);
+ }
+
+ @At("/:l2/:l3") @Delete
+ public Reply l2l3(@Named("dynamic") String dynamic,
+ @Named("l2") String second,
+ @Named("l3") String third) {
+ return Reply.with("delete:" + dynamic + "_" + second + "_" + third);
+ }
+
+ @At("/:l2/:l3") @Put
+ public Reply l2l3Put(@Named("dynamic") String dynamic,
+ @Named("l2") String second,
+ @Named("l3") String third) {
+ return Reply.with("put:" + dynamic + "_" + second + "_" + third);
+ }
+
+ @At("/:l2/:l3") @Post
+ public Reply l2l3Post(@Named("dynamic") String dynamic,
+ @Named("l2") String second,
+ @Named("l3") String third) {
+ return Reply.with("post:" + dynamic + "_" + second + "_" + third);
+ }
+
+ @At("/:l2/:l3") @Get
+ public Reply l2l3Get(@Named("dynamic") String dynamic,
+ @Named("l2") String second,
+ @Named("l3") String third) {
+ return Reply.with("get:" + dynamic + "_" + second + "_" + third);
+ }
+
+// BUG EXISTS WHEN 4-Level MIXED PATHS ARE INTRODUCED:
+
+ @At("/:l2/:l3/l4") @Get
+ public Reply l4Get(@Named("dynamic") String dynamic,
+ @Named("l2") String second,
+ @Named("l3") String third) {
+ return Reply.with("4l:get:" + dynamic + "_" + second + "_" + third);
+ }
+
+ @At("/:l2/:l3/l4") @Post
+ public Reply l4Post(@Named("dynamic") String dynamic,
+ @Named("l2") String second,
+ @Named("l3") String third) {
+ return Reply.with("4l:post:" + dynamic + "_" + second + "_" + third);
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/SelectRouting.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/SelectRouting.java
new file mode 100644
index 00000000..391899b2
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/SelectRouting.java
@@ -0,0 +1,118 @@
+package com.google.sitebricks.example;
+
+
+import com.google.sitebricks.At;
+import com.google.sitebricks.http.Delete;
+import com.google.sitebricks.http.Get;
+import com.google.sitebricks.http.Post;
+import com.google.sitebricks.http.Put;
+import com.google.sitebricks.http.Select;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+@At("/select") @Select("event")
+public class SelectRouting {
+
+ private List data = new ArrayList();
+
+ public SelectRouting() {
+ }
+
+ public SelectRouting(List data) {
+ this.data = data;
+ }
+
+ public List getData() {
+ return data;
+ }
+
+ public void setData(List data) {
+ this.data = data;
+ }
+
+ @Post
+ public void defaultPost() {
+ data.add("defaultPost");
+ }
+
+ @Post("foo")
+ public void fooPost() {
+ data.add("fooPost");
+ }
+
+ @Post("bar")
+ public void barPost() {
+ data.add("barPost");
+ }
+
+ @Post("304")
+ public Object redirectPost() {
+ data.add("redirectPost");
+ return new SelectRouting(data);
+ }
+
+ @Get
+ public void defaultGet() {
+ data.add("defaultGet");
+ }
+
+ @Get("foo")
+ public void fooGet() {
+ data.add("fooGet");
+ }
+
+ @Get("bar")
+ public void barGet() {
+ data.add("barGet");
+ }
+
+ @Get("304")
+ public Object redirectGet() {
+ data.add("redirectGet");
+ return new SelectRouting(data);
+ }
+
+ @Put
+ public void defaultPut() {
+ data.add("defaultPut");
+ }
+
+ @Put("foo")
+ public void fooPut() {
+ data.add("fooPut");
+ }
+
+ @Put("bar")
+ public void barPut() {
+ data.add("barPut");
+ }
+
+ @Put("304")
+ public Object redirectPut() {
+ data.add("redirectPut");
+ return new SelectRouting(data);
+ }
+
+ @Delete
+ public void defaultDelete() {
+ data.add("defaultDelete");
+ }
+
+ @Delete("foo")
+ public void fooDelete() {
+ data.add("fooDelete");
+ }
+
+ @Delete("bar")
+ public void barDelete() {
+ data.add("barDelete");
+ }
+
+ @Delete("304")
+ public Object redirectDelete() {
+ data.add("redirectDelete");
+ return new SelectRouting(data);
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/ShowIf.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/ShowIf.java
new file mode 100644
index 00000000..5a32ac66
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/ShowIf.java
@@ -0,0 +1,24 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.At;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@At("/showif")
+public class ShowIf {
+ private boolean show;
+ private String message = "Hello from google-sitebricks!";
+
+ public String getMessage() {
+ return message;
+ }
+
+ public boolean isShow() {
+ return show;
+ }
+
+ public void setShow(boolean show) {
+ this.show = show;
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/SitebricksConfig.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/SitebricksConfig.java
new file mode 100644
index 00000000..fdb8d17b
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/SitebricksConfig.java
@@ -0,0 +1,145 @@
+package com.google.sitebricks.example;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.BindingAnnotation;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Singleton;
+import com.google.inject.Stage;
+import com.google.inject.servlet.GuiceServletContextListener;
+import com.google.sitebricks.stat.StatModule;
+import com.google.sitebricks.AwareModule;
+import com.google.sitebricks.SitebricksModule;
+import com.google.sitebricks.binding.FlashCache;
+import com.google.sitebricks.binding.HttpSessionFlashCache;
+import com.google.sitebricks.conversion.DateConverters;
+import com.google.sitebricks.debug.DebugPage;
+import com.google.sitebricks.headless.Reply;
+import com.google.sitebricks.http.Get;
+import com.google.sitebricks.http.Post;
+import com.google.sitebricks.routing.Action;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+public class SitebricksConfig extends GuiceServletContextListener {
+
+ // a weird format
+ public static final String DEFAULT_DATE_TIME_FORMAT = "dd MM yy SS";
+
+@Override
+ protected Injector getInjector() {
+ return Guice.createInjector(Stage.DEVELOPMENT, new SitebricksModule() {
+
+ @Override
+ protected void configureSitebricks() {
+
+ // TODO(dhanji): find a way to run the suite again with this module installed.
+// install(new GaeModule());
+
+ bind(FlashCache.class).to(HttpSessionFlashCache.class).in(Singleton.class);
+
+ // TODO We should run the test suite once with this turned off and with scan() on.
+// scan(SitebricksConfig.class.getPackage());
+ bindExplicitly();
+ bindActions();
+
+ at("/no_annotations/service").serve(RestfulWebServiceNoAnnotations.class);
+ at("/debug").show(DebugPage.class);
+
+ bind(Start.class).annotatedWith(Test.class).to(Start.class);
+
+ // Localize using the default translation set (i.e. from the @Message annotations)
+ localize(I18n.MyMessages.class).usingDefault();
+ localize(I18n.MyMessages.class).using(Locale.CANADA_FRENCH,
+ ImmutableMap.of(I18n.HELLO, I18n.HELLO_IN_FRENCH));
+
+ install(new StatModule("/stats"));
+
+ converter(new DateConverters.DateStringConverter(DEFAULT_DATE_TIME_FORMAT));
+
+ install(new AwareModule() {
+ @Override
+ protected void configureLifecycle() {
+ observe(StartAware.class).asEagerSingleton();
+ }
+ });
+ }
+
+ private void bindExplicitly() {
+ //TODO explicit bindings should override scanned ones.
+ at("/").show(Start.class);
+ at("/hello").show(HelloWorld.class);
+ at("/case").show(Case.class);
+ at("/embed").show(Embed.class);
+ at("/error").show(CompileErrors.class);
+ at("/forms").show(Forms.class);
+ at("/repeat").show(Repeat.class);
+ at("/showif").show(ShowIf.class);
+ at("/dynamic.js").show(DynamicJs.class);
+
+ at("/conversion").show(Conversion.class);
+
+ at("/hiddenfieldmethod").show(HiddenFieldMethod.class);
+ at("/select").show(SelectRouting.class);
+ at("/conneg").show(ContentNegotiation.class);
+
+ at("/helloservice").serve(HelloWorldService.class);
+
+ at("/service").serve(RestfulWebService.class);
+ at("/postable").serve(PostableRestfulWebService.class);
+ at("/superpath").serve(RestfulWebServiceWithSubpaths.class);
+ at("/matrixpath").serve(RestfulWebServiceWithMatrixParams.class);
+ at("/superpath2/:dynamic").serve(RestfulWebServiceWithSubpaths2.class);
+ at("/json/:type").serve(RestfulWebServiceWithCRUD.class);
+ at("/jsonConversion").serve(RestfulWebServiceWithCRUDConversions.class);
+
+ at("/pagechain").show(PageChain.class);
+ at("/nextpage").show(NextPage.class);
+
+ at("/i18n").show(I18n.class);
+
+ // MVEL template.
+ at("/template/mvel").show(MvelTemplateExample.class);
+
+ // templating by extension
+ at("/template").show(DecoratedPage.class);
+
+ embed(HelloWorld.class).as("Hello");
+ }
+
+ private void bindActions() {
+ at("/spi/test")
+ .perform(action("get:top"))
+ .on(Get.class)
+ .perform(action("post:junk_subpath1"))
+ .on(Post.class);
+ }
+ });
+ }
+
+ private Action action(final String reply) {
+ return new Action() {
+ @Override
+ public boolean shouldCall(HttpServletRequest request) {
+ return true;
+ }
+
+ @Override
+ public Object call(Object page, Map map) {
+ return Reply.with(reply);
+ }
+ };
+ }
+
+ @BindingAnnotation
+ @Retention(RetentionPolicy.RUNTIME)
+ public static @interface Test {
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Start.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Start.java
new file mode 100644
index 00000000..411ea726
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/Start.java
@@ -0,0 +1,31 @@
+package com.google.sitebricks.example;
+
+import com.google.inject.Singleton;
+import com.google.sitebricks.stat.Stat;
+import com.google.sitebricks.At;
+import com.google.sitebricks.Show;
+import com.google.sitebricks.http.Get;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@At("/")
+@Show("index.html") @Singleton
+public class Start {
+ public static final String PAGE_LOADS = "page-loads";
+ public static volatile String HELLO_MSG = "YOU SHOULD NEVER SEE THIS!";
+ private String message = HELLO_MSG;
+
+ @Stat(PAGE_LOADS)
+ private final AtomicInteger pageLoads = new AtomicInteger();
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Get void display() {
+ pageLoads.incrementAndGet();
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/StartAware.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/StartAware.java
new file mode 100644
index 00000000..0b696a52
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/StartAware.java
@@ -0,0 +1,17 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.Aware;
+
+/**
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+public class StartAware implements Aware {
+ @Override
+ public void startup() {
+ HelloWorld.HELLO_MSG = "Hello from google-sitebricks!";
+ }
+
+ @Override
+ public void shutdown() {
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/TestPage.java b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/TestPage.java
new file mode 100644
index 00000000..86d50d96
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/java/com/google/sitebricks/example/TestPage.java
@@ -0,0 +1,28 @@
+package com.google.sitebricks.example;
+
+import com.google.sitebricks.At;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail com)
+ */
+@At("/test")
+public class TestPage {
+ private String message = "hello";
+ private boolean appear = true;
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public boolean isAppear() {
+ return appear;
+ }
+
+ public void setAppear(boolean appear) {
+ this.appear = appear;
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/main/resources/Case.html b/sitebricks-acceptance-tests/src/main/resources/Case.html
new file mode 100644
index 00000000..c3c34bf6
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/Case.html
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
examples
+
+
+ Example of the @Case widget, which is similar to a java switch() statement (we use it to choose between 3 colors):
+
+ @Case(choice=color)
+
+
+ @When("red")
+
+ Red
+
+
+ @When("yellow")
+
+ Yellow
+
+
+ @When("green")
+
+ Green
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/CompileErrors.html b/sitebricks-acceptance-tests/src/main/resources/CompileErrors.html
new file mode 100644
index 00000000..0f6df653
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/CompileErrors.html
@@ -0,0 +1,61 @@
+
+
+
+
+ @Require
+
+
+ @Require
+
+
+
+
+
+
+
+
+
+
+
+
+
+
examples
+
+
+
+ Message : ${message 3}
+
+
+
+
+
+
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/ContentNegotiation.html b/sitebricks-acceptance-tests/src/main/resources/ContentNegotiation.html
new file mode 100644
index 00000000..734a20d7
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/ContentNegotiation.html
@@ -0,0 +1,8 @@
+
+
+
+ ${content}
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/Conversion.html b/sitebricks-acceptance-tests/src/main/resources/Conversion.html
new file mode 100644
index 00000000..6623d5c6
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/Conversion.html
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
examples
+
+
+
+ The following demonstrates type conversion and binding to page object properties.
+
+
+ Bound:
+
+ datefield: ${date}
+
+
+
+ calendarfield: ${calendar}
+
+
+
+ messagefield: ${message}
+
+
+
+ doublefield: ${dbl}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/DecoratedPage.html b/sitebricks-acceptance-tests/src/main/resources/DecoratedPage.html
new file mode 100644
index 00000000..f5a0d690
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/DecoratedPage.html
@@ -0,0 +1,7 @@
+
+
+ This is in the extension
+
+ The extension is ${description}
+
+
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/main/resources/Decorator.html b/sitebricks-acceptance-tests/src/main/resources/Decorator.html
new file mode 100644
index 00000000..fa0461f0
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/Decorator.html
@@ -0,0 +1,19 @@
+
+
+
+ Text defined in the base class
+ ${hello}
+
+
+
+ Text defined in the subclass
+ ${world}
+
+
+
+ @Decorated
+ This should be replaced by the extension
+
+ This is just in the template
+
+
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/main/resources/Embed.html b/sitebricks-acceptance-tests/src/main/resources/Embed.html
new file mode 100644
index 00000000..c7f2abfe
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/Embed.html
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+ The following is a custom widget created from the HelloWorld example.
+ Notice that the stylesheet is available in the enclosing page via use of the @Require widget
+ (this page is not styled by default, whereas HelloWorld is).
+
+
+ We can even pass arguments to the embedded page (let's use the list "${arg}" instead of its
+ default message ).
+
+
+
+ @Repeat(var="item", items=arg)
+
+ @Hello(message=item)
+
+ This text is replaced by the embed.
+
+
+
+
+
+
+
Let's try that again!!
+
+
+
+ @Repeat(var="item", items=arg)
+
+ @Hello(message=item)
+
+ This text is replaced by the embed.
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/Forms.html b/sitebricks-acceptance-tests/src/main/resources/Forms.html
new file mode 100644
index 00000000..2b08d100
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/Forms.html
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
examples
+
+
+
+ The following demonstrates forms and binding to page object properties.
+
+
+
+
+
+ Bound:
+
+ textfield: ${text}
+
+
+
+ autobots: ${autobots}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/HelloWorld.html b/sitebricks-acceptance-tests/src/main/resources/HelloWorld.html
new file mode 100644
index 00000000..8c0a3560
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/HelloWorld.html
@@ -0,0 +1,67 @@
+
+
+
+
+
+ @Require
+
+
+ @Require
+
+
+
+
+
+
+
+
+
+
+
+
+
+
examples
+
+
+
+ Message : ${message}
+
+
+
+ Call a function: ${this.mangle(message)}
+
+
+
+
+
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/HiddenFieldMethod.html b/sitebricks-acceptance-tests/src/main/resources/HiddenFieldMethod.html
new file mode 100644
index 00000000..6e55ee40
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/HiddenFieldMethod.html
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+start header
+
+
+
+
+
+
+
examples
+
+
+
+ The following demonstrates a form with HTTP PUT method simulated via a hidden field..
+
+
+
+
+
+ Hint: POST button removes hidden field via javascript and then submits normally via POST
+
+ Bound:
+
+ textfield: ${text}
+
+ Message:
${putMessage}
+
+
+
+
+
+
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/I18n.html b/sitebricks-acceptance-tests/src/main/resources/I18n.html
new file mode 100644
index 00000000..509a2ccd
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/I18n.html
@@ -0,0 +1,68 @@
+
+
+
+
+ @Require
+
+
+ @Require
+
+
+
+
+
+
+
+
+
+
+
+
+
+
examples
+
+
+ Browser Locale: ${locale}
+
+
+
+
+
+ Localized message: ${message}
+
+
+
+
+
+
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/MvelTemplateExample.mvel b/sitebricks-acceptance-tests/src/main/resources/MvelTemplateExample.mvel
new file mode 100644
index 00000000..eb611d08
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/MvelTemplateExample.mvel
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
examples
+
+
+ Message : @{message}
+ (Rendered using MVEL orb tags. See http://mvel.codehaus.org)
+
+
+
+
+
+
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/NextPage.html b/sitebricks-acceptance-tests/src/main/resources/NextPage.html
new file mode 100644
index 00000000..ba5e858f
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/NextPage.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
examples
+
+
+ The next page in the chain received the value you typed in from the previous
+ page: ${persistedValue}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/PageChain.html b/sitebricks-acceptance-tests/src/main/resources/PageChain.html
new file mode 100644
index 00000000..c4aa024c
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/PageChain.html
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
examples
+
+
+ The following demonstrates page chaining. The value you type in will be
+ passed on to the next page on the server side, without leaking it to the
+ client.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/Repeat.html b/sitebricks-acceptance-tests/src/main/resources/Repeat.html
new file mode 100644
index 00000000..95f2002b
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/Repeat.html
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
examples
+
+
+ The following expression is repeated over every item in the bound collection of names.
+
+
+ @Repeat(items=names, var="name", pageVar="page")
+ ${index}: ${name} (last? ${isLast})
+
+
+
+
+
+
+
+
+ Repeat inside a repeat:
+
+
+ @Repeat(items=movies, var="movie")
+ @Repeat(items=movie.actors, var="actor")${actor}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/SelectRouting.html b/sitebricks-acceptance-tests/src/main/resources/SelectRouting.html
new file mode 100644
index 00000000..2ba053ec
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/SelectRouting.html
@@ -0,0 +1,228 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
examples
+
+ @Repeat(items=data, var="event")
+
${event}
+
+ The following demonstrates a routing mechanism using the Select annotation
+
+
+
GET
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
POST
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
PUT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
DELETE
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hint: POST button removes hidden field via javascript and then submits normally via POST
+
+ Bound:
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/ShowIf.html b/sitebricks-acceptance-tests/src/main/resources/ShowIf.html
new file mode 100644
index 00000000..f6a929a5
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/ShowIf.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
examples
+
+
+ The following span tag will only be shown if query parameter "show" is true.
+
+ @ShowIf(show)
+
+ Message : ${message}
+
+
+
+ toggle
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/TestPage.html b/sitebricks-acceptance-tests/src/main/resources/TestPage.html
new file mode 100644
index 00000000..9e83a954
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/TestPage.html
@@ -0,0 +1,8 @@
+
+
+ show/hide
+
+ @ShowIf(appear)
+ ${message} from warp-widgets!
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/WEB-INF/web.xml b/sitebricks-acceptance-tests/src/main/resources/WEB-INF/web.xml
new file mode 100644
index 00000000..16759ef1
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/WEB-INF/web.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ webFilter
+ com.google.inject.servlet.GuiceFilter
+
+
+
+ webFilter
+ /*
+
+
+
+ com.google.sitebricks.example.SitebricksConfig
+
+
+
diff --git a/sitebricks-acceptance-tests/src/main/resources/default.css b/sitebricks-acceptance-tests/src/main/resources/default.css
new file mode 100644
index 00000000..f4543962
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/default.css
@@ -0,0 +1,339 @@
+/*
+Design by Free CSS Templates
+http://www.freecsstemplates.org
+Released for free under a Creative Commons Attribution 2.5 License
+*/
+
+body {
+ margin: 100px 0 0 0;
+ padding: 0;
+ background: #FFFFFF url(images/img01.gif) repeat-x;
+ font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
+ font-size: 13px;
+ color: #333333;
+}
+
+h1, h2, h3 {
+ margin: 0;
+ text-transform: lowercase;
+ font-weight: normal;
+ color: #3E3E3E;
+}
+
+h1 {
+ letter-spacing: -1px;
+ font-size: 32px;
+}
+
+h2 {
+ font-size: 23px;
+}
+
+p, ul, ol {
+ margin: 0 0 2em 0;
+ text-align: justify;
+ line-height: 26px;
+ font-size: 11px;
+}
+
+a:link {
+ color: #7BAA0F;
+}
+
+a:hover, a:active {
+ text-decoration: none;
+ color: #003448;
+}
+
+a:visited {
+ color: #333333;
+}
+
+img {
+ border: none;
+}
+
+img.left {
+ float: left;
+ margin-right: 15px;
+}
+
+img.right {
+ float: right;
+ margin-left: 15px;
+}
+
+/* Form */
+
+form {
+ margin: 0;
+ padding: 0;
+}
+
+fieldset {
+ margin: 0;
+ padding: 0;
+ border: none;
+}
+
+legend {
+ display: none;
+}
+
+input, textarea, select {
+ font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
+ font-size: 13px;
+ color: #333333;
+}
+
+/* Header */
+
+#header {
+ width: 850px;
+ height: 82px;
+ margin: 0 auto 40px auto;
+ background: url(images/img03.gif) repeat-x left bottom;
+}
+
+#logo {
+ float: left;
+}
+
+#logo h1 {
+ font-size: 38px;
+ color: #494949;
+}
+
+#logo h1 sup {
+ vertical-align: text-top;
+ font-size: 24px;
+}
+
+#logo h1 a {
+ color: #494949;
+}
+
+#logo h2 {
+ margin-top: -10px;
+ font-size: 12px;
+ color: #A0A0A0;
+}
+
+#logo a {
+ text-decoration: none;
+}
+
+/* Menu */
+
+#menu {
+ float: right;
+}
+
+#menu ul {
+ margin: 0;
+ padding: 15px 0 0 0;
+ list-style: none;
+}
+
+#menu li {
+ display: inline;
+}
+
+#menu a {
+ display: block;
+ float: left;
+ background: #F5F5F5 url(images/img02.gif) repeat-x left bottom;
+ margin-left: 5px;
+ padding: 7px 20px;
+ text-decoration: none;
+ font-size: 13px;
+ color: #000000;
+}
+
+#menu a:hover {
+ text-decoration: underline;
+}
+
+#menu .active a {
+}
+
+/* Page */
+
+#page {
+ width: 850px;
+ margin: 0 auto;
+}
+
+/* Content */
+
+#content {
+ float: left;
+ width: 575px;
+}
+
+/* Post */
+
+.post {
+}
+
+.post .title {
+ margin-bottom: 20px;
+ padding-bottom: 5px;
+ background: url(images/img11.gif) no-repeat right 50%;
+ border-bottom: 1px dotted #D1D1D1;
+}
+
+.post .entry {
+}
+
+.post .entry li {
+ color: #7BAA0F;
+}
+
+.post .meta {
+ padding: 15px 0 60px 0;
+ background: url(images/img03.gif) repeat-x;
+}
+
+.post .meta p {
+ margin: 0;
+ line-height: normal;
+ color: #999999;
+}
+
+.post .meta .byline {
+ float: left;
+}
+
+.post .meta .links {
+ float: right;
+}
+
+.post .meta .more {
+ padding: 0 20px 0 18px;
+ background: url(images/img06.gif) no-repeat left center;
+}
+
+.comments {
+ padding-left: 22px;
+ background: url(images/img07.gif) no-repeat left center;
+}
+
+.post .meta .comments {
+ padding-left: 22px;
+ background: url(images/img07.gif) no-repeat left center;
+}
+
+.post .meta b {
+ display: none;
+}
+
+/* Sidebar */
+
+#sidebar {
+ float: right;
+ width: 195px;
+}
+
+#sidebar ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+#sidebar li {
+ margin-bottom: 40px;
+}
+
+#sidebar li ul {
+}
+
+#sidebar li li {
+ margin: 0;
+ padding-left: 12px;
+ background: url(images/img12.gif) no-repeat left 50%;
+}
+
+#sidebar h2 {
+ margin-bottom: 10px;
+ background: url(images/img11.gif) no-repeat right 50%;
+ border-bottom: 1px dotted #D1D1D1;
+ font-size: 16px;
+}
+
+/* Search */
+
+#search {
+}
+
+#search h2 {
+ margin-bottom: 20px;
+}
+
+#s {
+ width: 120px;
+ margin-right: 5px;
+ padding: 3px;
+ border: 1px solid #F0F0F0;
+}
+
+#x {
+ padding: 3px;
+ background: #ECECEC url(images/img08.gif) repeat-x left bottom;
+ border: none;
+ text-transform: lowercase;
+ font-size: 11px;
+ color: #4F4F4F;
+}
+
+/* Boxes */
+
+.box1 {
+ padding: 20px;
+ background: url(images/img05.gif) no-repeat;
+}
+
+.box2 {
+ color: #BABABA;
+}
+
+.box2 h2 {
+ margin-bottom: 15px;
+ background: url(images/img10.gif) repeat-x left bottom;
+ font-size: 16px;
+ color: #FFFFFF;
+}
+
+.box2 ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.box2 a:link, .box2 a:hover, .box2 a:active, .box2 a:visited {
+ color: #EDEDED;
+}
+
+/* Footer */
+
+#footer {
+ height: 400px;
+ min-height: 74px;
+ padding: 10px 0 0 0;
+ background: #003448 url(images/img09.gif) repeat-x;
+}
+
+html>body #footer {
+ height: auto;
+}
+
+#legal {
+ clear: both;
+ padding-top: 20px;
+ text-align: center;
+ color: #8ADE3C;
+}
+
+#legal a {
+ color: #76D424;
+}
diff --git a/sitebricks-acceptance-tests/src/main/resources/dynamic.js b/sitebricks-acceptance-tests/src/main/resources/dynamic.js
new file mode 100644
index 00000000..f437cfaf
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/dynamic.js
@@ -0,0 +1,3 @@
+function hello() {
+ alert("${message}");
+}
diff --git a/sitebricks-acceptance-tests/src/main/resources/index.html b/sitebricks-acceptance-tests/src/main/resources/index.html
new file mode 100644
index 00000000..9d6789be
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/main/resources/index.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
examples
+
+
+
+
+ (How to hide stuff)
+ (How to iterate collections)
+ (textfield, chooser, etc.)
+ (Authoring a new brick)
+ (Like java's switch)
+ (non-XML templates)
+
+ (passing state across pages using
+ post + redirect)
+
+ (simulates HTTP PUT method via a hidden field)
+ (using browser locale)
+ (using MVEL templates instead of sitebricks)
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/ServletContainerIntegrationTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/ServletContainerIntegrationTest.java
new file mode 100644
index 00000000..c68be2bb
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/ServletContainerIntegrationTest.java
@@ -0,0 +1,48 @@
+package com.google.sitebricks;
+
+import org.mortbay.jetty.Handler;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.servlet.FilterHolder;
+import org.mortbay.jetty.servlet.ServletHandler;
+
+import javax.servlet.*;
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+public class ServletContainerIntegrationTest {
+
+// @NOTaTest
+ public final void fireUp() throws Exception {
+
+ final Server server = new Server(8085);
+ final ServletHandler servletHandler = new ServletHandler();
+ servletHandler.addFilterWithMapping(new FilterHolder(new Filter() {
+ public void init(FilterConfig filterConfig) throws ServletException {
+ System.out.println("*************************************************");
+ final Set resourcePaths = filterConfig.getServletContext().getResourcePaths("/WEB-INF/classes");
+
+
+ System.out.println(resourcePaths);
+
+ System.out.println("*************************************************");
+ }
+
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+ System.out.println("*************************************************");
+ System.out.println("Hello!");
+ System.out.println("*************************************************");
+ }
+
+ public void destroy() {
+ }
+ }), "/*", Handler.REQUEST);
+
+ server.addHandler(servletHandler);
+
+ server.start();
+ server.join();
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/CaseAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/CaseAcceptanceTest.java
new file mode 100644
index 00000000..9b05002d
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/CaseAcceptanceTest.java
@@ -0,0 +1,19 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.sitebricks.acceptance.page.CasePage;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.openqa.selenium.WebDriver;
+import org.testng.annotations.Test;
+
+/**
+ * @author Tom Wilson (tom@tomwilson.name)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class CaseAcceptanceTest {
+ public void shouldDisplayGreenFromCaseStatement() {
+ WebDriver driver = AcceptanceTest.createWebDriver();
+ CasePage page = CasePage.open(driver);
+
+ assert "Green".equals(page.getDisplayedColor()) : "expected color wasn't displayed";
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/ConnegAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/ConnegAcceptanceTest.java
new file mode 100644
index 00000000..0ec2933f
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/ConnegAcceptanceTest.java
@@ -0,0 +1,41 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.sitebricks.acceptance.page.ConnegPage;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.testng.annotations.Test;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class ConnegAcceptanceTest {
+
+ public void shouldReturnGifByDefault() {
+ ConnegPage page = ConnegPage.openWithHeaders(ImmutableMap.of());
+
+ assert page.hasContent("GIF")
+ : "Did not have gif!";
+ }
+
+ public void shouldReturnJpeg() {
+ ConnegPage page = ConnegPage.openWithHeaders(ImmutableMap.of("Accept", "image/jpeg"));
+
+ assert page.hasContent("JPEG")
+ : "Did not have jpeg!";
+ }
+
+ public void shouldReturnPng() {
+ ConnegPage page = ConnegPage.openWithHeaders(ImmutableMap.of("Accept", "image/png"));
+
+ assert page.hasContent("PNG")
+ : "Did not have jpeg!";
+ }
+
+ public void shouldReturnPngOrJpeg() {
+ ConnegPage page = ConnegPage.openWithHeaders(ImmutableMap.of("Accept", "image/png, image/jpeg"));
+
+ assert page.hasContent("PNG") || page.hasContent("JPEG")
+ : "Did not have jpeg!";
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/ConversionAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/ConversionAcceptanceTest.java
new file mode 100644
index 00000000..dc8546da
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/ConversionAcceptanceTest.java
@@ -0,0 +1,31 @@
+package com.google.sitebricks.acceptance;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import org.openqa.selenium.WebDriver;
+import org.testng.annotations.Test;
+
+import com.google.sitebricks.acceptance.page.ConversionPage;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.example.SitebricksConfig;
+
+@Test(suiteName = AcceptanceTest.SUITE)
+public class ConversionAcceptanceTest {
+
+ public void hasConvertedTypes() {
+ String inboundDateFormat = SitebricksConfig.DEFAULT_DATE_TIME_FORMAT;
+ Date date = new Date();
+ Calendar calendar = Calendar.getInstance();
+ String msg = "This is a test msg";
+ Double dbl = 2.2;
+
+ WebDriver driver = AcceptanceTest.createWebDriver();
+ ConversionPage page = ConversionPage.open(driver, date, calendar, inboundDateFormat, msg, dbl);
+
+ assert page.hasCalendar(calendar) : "Calendar not bound correctly";
+ assert page.hasDate(date) : "Date not bound correctly";
+ assert page.hasDouble(dbl) : "Double nto bound correctly";
+ assert page.hasMessage(msg) : "String not bound correctly";
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/DecoratorAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/DecoratorAcceptanceTest.java
new file mode 100644
index 00000000..7448a0d4
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/DecoratorAcceptanceTest.java
@@ -0,0 +1,25 @@
+package com.google.sitebricks.acceptance;
+
+import org.openqa.selenium.WebDriver;
+import org.testng.annotations.Test;
+
+import com.google.sitebricks.acceptance.page.DecoratorPage;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class DecoratorAcceptanceTest {
+
+ public void shouldRenderHelloWorldEmbeddedWithRequires() {
+ WebDriver driver = AcceptanceTest.createWebDriver();
+ DecoratorPage page = DecoratorPage.open(driver);
+
+ assert page.hasBasePageText();
+ assert page.hasSubclassVariable();
+ assert page.hasSubclassText();
+ assert page.hasSubclassVariableInTemplate();
+ assert page.hasBasePageVariable();
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/DynamicJsAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/DynamicJsAcceptanceTest.java
new file mode 100644
index 00000000..d9393ffb
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/DynamicJsAcceptanceTest.java
@@ -0,0 +1,20 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.sitebricks.acceptance.page.DynamicJsPage;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.openqa.selenium.WebDriver;
+import org.testng.annotations.Test;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class DynamicJsAcceptanceTest {
+
+ public void shouldRenderDynamicTextFromJsTemplate() {
+ WebDriver driver = AcceptanceTest.createWebDriver();
+ DynamicJsPage page = DynamicJsPage.open(driver);
+
+ assert page.hasDynamicText() : "Did not generate dynamic text from warp-widget";
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/EmbedAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/EmbedAcceptanceTest.java
new file mode 100644
index 00000000..538e9b0a
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/EmbedAcceptanceTest.java
@@ -0,0 +1,21 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.sitebricks.acceptance.page.EmbedPage;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.openqa.selenium.WebDriver;
+import org.testng.annotations.Test;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class EmbedAcceptanceTest {
+
+ public void shouldRenderHelloWorldEmbeddedWithRequires() {
+ WebDriver driver = AcceptanceTest.createWebDriver();
+ EmbedPage page = EmbedPage.open(driver);
+
+ assert page.hasCssLink() : "Did not contain require widget (css link) from embedded page";
+ assert page.hasHelloWorldMessage() : "Did not contain hellow world message";
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/FormsAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/FormsAcceptanceTest.java
new file mode 100644
index 00000000..f9742af6
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/FormsAcceptanceTest.java
@@ -0,0 +1,31 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.sitebricks.acceptance.page.FormsPage;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.openqa.selenium.WebDriver;
+import org.testng.annotations.Test;
+
+import java.util.Date;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class FormsAcceptanceTest {
+ private static final String SOME_TEXT = "aoskdopaksdoaskd" + new Date();
+
+ public void shouldRenderDynamicTextFromTextFieldBinding() {
+ WebDriver driver = AcceptanceTest.createWebDriver();
+ FormsPage page = FormsPage.open(driver);
+
+ final String boundAutobots = "Optimus, Rodimus, UltraMagnus";
+ final String[] strings = boundAutobots.split(", ");
+
+ page.enterText(SOME_TEXT);
+ page.enterAutobots(strings[0], strings[1], strings[2]);
+ page.send();
+
+ assert page.hasBoundText(SOME_TEXT) : "Did not generate dynamic text from form binding";
+ assert page.hasBoundAutobots(boundAutobots) : "Did not generate text from list binding";
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/HelloWorldAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/HelloWorldAcceptanceTest.java
new file mode 100644
index 00000000..6263e8bd
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/HelloWorldAcceptanceTest.java
@@ -0,0 +1,41 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.sitebricks.acceptance.page.HelloWorldPage;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.openqa.selenium.WebDriver;
+import org.testng.annotations.Test;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class HelloWorldAcceptanceTest {
+
+ public void shouldRenderDynamicTextFromHelloWorld() {
+ WebDriver driver = AcceptanceTest.createWebDriver();
+ HelloWorldPage page = HelloWorldPage.open(driver, "/hello");
+
+ assertHelloWorldContent(page);
+ }
+
+ public void shouldRenderDynamicTextFromHelloWorldService() {
+ WebDriver driver = AcceptanceTest.createWebDriver();
+ HelloWorldPage page = HelloWorldPage.open(driver, "/helloservice");
+
+ assertHelloWorldContent(page);
+ }
+
+ public void shouldRenderDynamicTextFromHelloWorldServiceDirect() {
+ WebDriver driver = AcceptanceTest.createWebDriver();
+ HelloWorldPage page = HelloWorldPage.open(driver, "/helloservice/direct");
+
+ assertHelloWorldContent(page);
+ }
+
+ private void assertHelloWorldContent(HelloWorldPage page) {
+ assert page.hasHelloWorldMessage() : "Did not generate dynamic text from el expression";
+ assert page.hasCorrectDoctype() : "Did not contain the expected doctype declaration at the start of the HTML file";
+ assert page.hasMangledString() : "Did not contain method-generated string";
+ assert page.hasNonSelfClosingScriptTag() : "Did not contain proper script tag with closing tag";
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/HiddenFieldMethodAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/HiddenFieldMethodAcceptanceTest.java
new file mode 100644
index 00000000..f9fb8258
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/HiddenFieldMethodAcceptanceTest.java
@@ -0,0 +1,25 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.sitebricks.acceptance.page.HiddenFieldMethodPage;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.openqa.selenium.WebDriver;
+import org.testng.annotations.Test;
+
+/**
+ * @author Peter Knego
+ */
+
+@Test(suiteName = AcceptanceTest.SUITE)
+public class HiddenFieldMethodAcceptanceTest {
+
+ public void shouldRenderDynamicTextFromTextFieldBinding() {
+ WebDriver driver = AcceptanceTest.createWebDriver();
+ HiddenFieldMethodPage page = HiddenFieldMethodPage.open(driver);
+
+ page.enterText("just some text");
+ page.submitPut();
+
+ // was the message generated via PUT method?
+ assert page.isPutMessage();
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/I18nAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/I18nAcceptanceTest.java
new file mode 100644
index 00000000..733c74a8
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/I18nAcceptanceTest.java
@@ -0,0 +1,25 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.sitebricks.acceptance.page.I18nPage;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.testng.annotations.Test;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class I18nAcceptanceTest {
+
+ public void shouldRenderLocalizedDynamicTextEnUs() {
+ I18nPage page = I18nPage.openWithHeaders(ImmutableMap.of("Accept-Language", "en-US"));
+
+ assert page.hasHelloTo(I18nPage.NAME) : "Did not generate dynamic text from i18n message set";
+ }
+
+ public void shouldRenderLocalizedDynamicTextFrCa() {
+ I18nPage page = I18nPage.openWithHeaders(ImmutableMap.of("Accept-Language", "fr-CA"));
+
+ assert page.hasBonjourTo(I18nPage.NAME) : "Did not generate dynamic text from i18n message set";
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/JettyAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/JettyAcceptanceTest.java
new file mode 100644
index 00000000..382db02b
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/JettyAcceptanceTest.java
@@ -0,0 +1,40 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.acceptance.util.Jetty;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+import java.io.File;
+
+/**
+ * @author Tom Wilson (tom@tomwilson.name)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class JettyAcceptanceTest {
+ private static final String BUILDR_RESOURCE_DIR = "acceptance-test/src/main/resources";
+ private static final String STD_RESOURCE_DIR = "src/main/resources";
+
+ private Jetty server;
+
+ public JettyAcceptanceTest() {
+ File standardDir = new File(STD_RESOURCE_DIR);
+
+ if (standardDir.exists()) {
+ server = new Jetty(STD_RESOURCE_DIR);
+ } else {
+ server = new Jetty(BUILDR_RESOURCE_DIR);
+ }
+ }
+
+ @BeforeSuite
+ public void start() throws Exception {
+ server.start();
+ }
+
+ @AfterSuite
+ public void stop() throws Exception {
+ server.stop();
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/PageChainingAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/PageChainingAcceptanceTest.java
new file mode 100644
index 00000000..02bbe7f9
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/PageChainingAcceptanceTest.java
@@ -0,0 +1,31 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.sitebricks.acceptance.page.PageChainPage;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.testng.annotations.Test;
+
+import java.util.Date;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class PageChainingAcceptanceTest {
+ private static final String SOME_TEXT = "some random textina" + new Date();
+
+ public void shouldPassEnteredTextToNextPage() {
+ WebDriver driver = AcceptanceTest.createWebDriver();
+ PageChainPage page = PageChainPage.open(driver);
+
+ page.enterText(SOME_TEXT);
+ page.next();
+
+ // We should now be on the NextPage page
+ assert driver.findElement(By.xpath("//div[@class='entry']"))
+ .getText()
+ .contains(SOME_TEXT)
+ : "Value did not get passed via page chaining to next page";
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/PostableRestfuWebServiceAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/PostableRestfuWebServiceAcceptanceTest.java
new file mode 100644
index 00000000..3ea67866
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/PostableRestfuWebServiceAcceptanceTest.java
@@ -0,0 +1,49 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.client.Web;
+import com.google.sitebricks.client.WebResponse;
+import com.google.sitebricks.client.transport.Json;
+import com.google.sitebricks.conversion.Converter;
+import com.google.sitebricks.conversion.ConverterRegistry;
+import com.google.sitebricks.conversion.StandardTypeConverter;
+import com.google.sitebricks.example.RestfulWebService;
+import org.testng.annotations.Test;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class PostableRestfuWebServiceAcceptanceTest {
+
+ public void shouldTransportJsonWithoutTemplate() {
+ RestfulWebService.Book perdido = new RestfulWebService.Book();
+ perdido.setAuthor(RestfulWebService.CHINA_MIEVILLE);
+ perdido.setName(RestfulWebService.PERDIDO_STREET_STATION);
+ perdido.setPageCount(RestfulWebService.PAGE_COUNT);
+
+
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/postable?p1=v1,v2")
+ .transports(RestfulWebService.Book.class)
+ .over(Json.class)
+ .post(perdido);
+
+ // Should ping us the author back.
+ assert response.toString().contains(perdido.getAuthor()) : response.toString();
+ }
+
+ private Injector createInjector() {
+ return Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bind(ConverterRegistry.class).toInstance(new StandardTypeConverter(
+ ImmutableSet.of()));
+ }
+ });
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RepeatAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RepeatAcceptanceTest.java
new file mode 100644
index 00000000..1deff94e
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RepeatAcceptanceTest.java
@@ -0,0 +1,40 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.sitebricks.acceptance.page.RepeatPage;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.apache.commons.collections.CollectionUtils;
+import org.openqa.selenium.WebDriver;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Tom Wilson (tom@tomwilson.name)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class RepeatAcceptanceTest {
+
+ public void shouldRepeatItemsFromCollection() {
+ WebDriver driver = AcceptanceTest.createWebDriver();
+ RepeatPage page = RepeatPage.open(driver);
+
+ List expectedNames = Arrays.asList(
+ "0: Dhanji (last? false)",
+ "1: Josh (last? false)",
+ "2: Jody (last? false)",
+ "3: Iron Man (last? true)");
+ List expectedMovies =
+ Arrays.asList("Dhanji Josh Jody Iron Man",
+ "Dhanji Josh Jody Iron Man",
+ "Dhanji Josh Jody Iron Man");
+
+ List actualNames = page.getRepeatedNames();
+ List actualMovies = page.getRepeatedMovies();
+
+ assert CollectionUtils.isEqualCollection(expectedNames, actualNames)
+ : "repeated names didn't match what was expected";
+ assert CollectionUtils.isEqualCollection(expectedMovies, actualMovies)
+ : "repeated movies didn't match what was expected";
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceAcceptanceTest.java
new file mode 100644
index 00000000..fb360358
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceAcceptanceTest.java
@@ -0,0 +1,99 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.client.Web;
+import com.google.sitebricks.client.WebResponse;
+import com.google.sitebricks.client.transport.Json;
+import com.google.sitebricks.client.transport.Text;
+import com.google.sitebricks.conversion.Converter;
+import com.google.sitebricks.conversion.ConverterRegistry;
+import com.google.sitebricks.conversion.StandardTypeConverter;
+import com.google.sitebricks.example.RestfulWebService;
+import org.testng.annotations.Test;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class RestfuWebServiceAcceptanceTest {
+
+ public void shouldTransportJsonWithoutTemplate() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/service")
+ .transports(String.class)
+ .over(Json.class)
+ .get();
+
+ assertBookResponse(response);
+ }
+
+
+ private Injector createInjector() {
+ return Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bind(ConverterRegistry.class).toInstance(new StandardTypeConverter(
+ ImmutableSet.of()));
+ }
+ });
+ }
+
+ public void shouldRedirect() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/service")
+ .transports(String.class)
+ .over(Text.class)
+ .post("");
+
+ assertRedirectResponse(response);
+ }
+
+ public void shouldTransportJsonWithoutTemplateNoAnnotations() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/no_annotations/service")
+ .transports(String.class)
+ .over(Json.class)
+ .get();
+
+ assertBookResponse(response);
+ }
+
+ public void shouldRedirectNoAnnotations() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/no_annotations/service")
+ .transports(String.class)
+ .over(Text.class)
+ .post("");
+
+ assertRedirectResponse(response);
+ }
+
+ private static void assertRedirectResponse(WebResponse response) {
+ assert HttpServletResponse.SC_MOVED_TEMPORARILY == response.status() : response.toString();
+ assert response.getHeaders().get("Location").endsWith("/other");
+ }
+
+ private static void assertBookResponse(WebResponse response) {
+ assert HttpServletResponse.SC_OK == response.status();
+
+ // Make sure the headers were set.
+ assert response.getHeaders().containsKey("hi");
+ assert "there".equals(response.getHeaders().get("hi"));
+ assert response.getHeaders().containsKey("Content-Type");
+
+ // assert stuff about the content itself.
+ RestfulWebService.Book book = response.to(RestfulWebService.Book.class).using(Json.class);
+ assert RestfulWebService.CHINA_MIEVILLE.equals(book.getAuthor());
+ assert RestfulWebService.PERDIDO_STREET_STATION.equals(book.getName());
+ assert RestfulWebService.PAGE_COUNT == book.getPageCount();
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithCRUDAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithCRUDAcceptanceTest.java
new file mode 100644
index 00000000..5ecab23f
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithCRUDAcceptanceTest.java
@@ -0,0 +1,104 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.client.Web;
+import com.google.sitebricks.client.WebResponse;
+import com.google.sitebricks.client.transport.Json;
+import com.google.sitebricks.conversion.Converter;
+import com.google.sitebricks.conversion.ConverterRegistry;
+import com.google.sitebricks.conversion.StandardTypeConverter;
+
+import org.testng.annotations.Test;
+
+import static com.google.sitebricks.example.RestfulWebServiceWithCRUD.BASE_SERVICE_PATH;
+import static com.google.sitebricks.example.RestfulWebServiceWithCRUD.CREATE;
+import static com.google.sitebricks.example.RestfulWebServiceWithCRUD.DELETE;
+import static com.google.sitebricks.example.RestfulWebServiceWithCRUD.READ_COLLECTION;
+import static com.google.sitebricks.example.RestfulWebServiceWithCRUD.READ_INDIVIDUAL;
+import static com.google.sitebricks.example.RestfulWebServiceWithCRUD.UPDATE;
+
+/**
+ * @author Jason van Zyl
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class RestfuWebServiceWithCRUDAcceptanceTest {
+
+ public void create() {
+ String url = AcceptanceTest.BASE_URL + BASE_SERVICE_PATH;
+ System.out.println("POST " + url);
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(url)
+ .transports(String.class)
+ .over(Json.class)
+ .post("");
+
+ assert CREATE.equals(response.toString()) : response.toString();
+ }
+
+ public void readCollection() {
+ String url = AcceptanceTest.BASE_URL + BASE_SERVICE_PATH;
+ System.out.println("GET " + url);
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(url)
+ .transports(String.class)
+ .over(Json.class)
+ .get();
+
+ assert READ_COLLECTION.equals(response.toString());
+ }
+
+ public void readIndividual() {
+ String url = AcceptanceTest.BASE_URL + BASE_SERVICE_PATH + "/1";
+ System.out.println("GET " + url);
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(url)
+ .transports(String.class)
+ .over(Json.class)
+ .get();
+
+ assert READ_INDIVIDUAL.equals(response.toString()) : response.toString();
+ }
+
+ public void update() {
+ String url = AcceptanceTest.BASE_URL + BASE_SERVICE_PATH + "/1";
+ System.out.println("PUT " + url);
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(url)
+ .transports(String.class)
+ .over(Json.class)
+ .put("");
+
+ assert UPDATE.equals(response.toString());
+ }
+
+ public void delete() {
+ String url = AcceptanceTest.BASE_URL + BASE_SERVICE_PATH + "/1";
+ System.out.println("DELETE " + url);
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(url)
+ .transports(String.class)
+ .over(Json.class)
+ .delete();
+
+ assert DELETE.equals(response.toString());
+ }
+
+
+ private Injector createInjector() {
+ return Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bind(ConverterRegistry.class).toInstance(new StandardTypeConverter(
+ ImmutableSet.of()));
+ }
+ });
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithCRUDConversionsAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithCRUDConversionsAcceptanceTest.java
new file mode 100644
index 00000000..6e591c56
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithCRUDConversionsAcceptanceTest.java
@@ -0,0 +1,111 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.client.Web;
+import com.google.sitebricks.client.WebResponse;
+import com.google.sitebricks.client.transport.Json;
+import com.google.sitebricks.conversion.Converter;
+import com.google.sitebricks.conversion.ConverterRegistry;
+import com.google.sitebricks.conversion.StandardTypeConverter;
+import com.google.sitebricks.example.RestfulWebServiceWithCRUDConversions;
+import com.google.sitebricks.example.RestfulWebServiceWithCRUDConversions.Widget;
+import org.testng.annotations.Test;
+
+import java.util.Date;
+import java.util.List;
+
+@Test(suiteName = AcceptanceTest.SUITE)
+public class RestfuWebServiceWithCRUDConversionsAcceptanceTest {
+ private Widget testWidget = new Widget(100, "Widget 100", new Date(), 1.50);
+ private Widget widgetOne = RestfulWebServiceWithCRUDConversions.findWidget(1).clone();
+
+ public void create() {
+ String url = AcceptanceTest.BASE_URL + RestfulWebServiceWithCRUDConversions.AT_ME;
+ System.out.println("POST " + url);
+
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(url)
+ .transports(Widget.class)
+ .over(Json.class)
+ .post(testWidget);
+
+ Widget result = response.to(Widget.class).using(Json.class);
+ assert result.equals(testWidget);
+ }
+
+ public void readCollection() {
+ String url = AcceptanceTest.BASE_URL + RestfulWebServiceWithCRUDConversions.AT_ME;
+ System.out.println("GET " + url);
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(url)
+ .transports(String.class)
+ .over(Json.class).get();
+
+ @SuppressWarnings("unchecked")
+ List result = response.to(List.class).using(Json.class);
+
+ assert result.size() == RestfulWebServiceWithCRUDConversions.widgets.size();
+ }
+
+ private Injector createInjector() {
+ return Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bind(ConverterRegistry.class).toInstance(new StandardTypeConverter(
+ ImmutableSet.of()));
+ }
+ });
+ }
+
+ public void readIndividual() {
+ String url = AcceptanceTest.BASE_URL + RestfulWebServiceWithCRUDConversions.AT_ME + "/" + widgetOne.getId();
+ System.out.println("GET " + url);
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(url)
+ .transports(String.class)
+ .over(Json.class)
+ .get();
+
+ Widget result = response.to(Widget.class).using(Json.class);
+ assert result.equals(widgetOne);
+ }
+
+ public void update() {
+ String url = AcceptanceTest.BASE_URL + RestfulWebServiceWithCRUDConversions.AT_ME;
+
+ widgetOne.setPrice(5.50);
+ System.out.println("PUT " + url);
+
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(url)
+ .transports(Widget.class)
+ .over(Json.class)
+ .put(widgetOne);
+
+ Widget result = response.to(Widget.class).using(Json.class);
+ assert result.equals(widgetOne);
+ }
+
+
+ public void delete() {
+ String url = AcceptanceTest.BASE_URL + RestfulWebServiceWithCRUDConversions.AT_ME + "/" + testWidget.getId();
+ System.out.println("DELETE " + url);
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(url)
+ .transports(String.class)
+ .over(Json.class)
+ .delete();
+
+ Widget result = response.to(Widget.class).using(Json.class);
+ assert result.equals(testWidget);
+ }
+
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithMatrixParamsAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithMatrixParamsAcceptanceTest.java
new file mode 100644
index 00000000..18097b5f
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithMatrixParamsAcceptanceTest.java
@@ -0,0 +1,57 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.client.Web;
+import com.google.sitebricks.client.WebResponse;
+import com.google.sitebricks.client.transport.Json;
+import com.google.sitebricks.conversion.Converter;
+import com.google.sitebricks.conversion.ConverterRegistry;
+import com.google.sitebricks.conversion.StandardTypeConverter;
+import com.google.sitebricks.example.RestfulWebServiceWithMatrixParams;
+import org.testng.annotations.Test;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class RestfuWebServiceWithMatrixParamsAcceptanceTest {
+
+ public void shouldServiceTopLevelPath() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/matrixpath")
+ .transports(String.class)
+ .over(Json.class)
+ .get();
+
+ assert RestfulWebServiceWithMatrixParams.TOPLEVEL.equals(response.toString())
+ : response.toString();
+ }
+
+ public void shouldServiceVariableThreeLevelSubPath() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/matrixpath/val;param1=val1;param2=val2/athing")
+ .transports(String.class)
+ .over(Json.class)
+ .post("");
+
+ assert ("{param1=[val1], param2=[val2]}" // Multimap of matrix params
+ + "_" + "val;param1=val1;param2=val2" // Variable path fragment #1
+ + "_" + "athing") // Variable path fragment #2
+ .equals(response.toString()) : response.toString();
+ }
+
+ private Injector createInjector() {
+ return Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bind(ConverterRegistry.class).toInstance(new StandardTypeConverter(
+ ImmutableSet.of()));
+ }
+ });
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithSubpaths2AcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithSubpaths2AcceptanceTest.java
new file mode 100644
index 00000000..a712ba9b
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithSubpaths2AcceptanceTest.java
@@ -0,0 +1,167 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.client.Web;
+import com.google.sitebricks.client.WebResponse;
+import com.google.sitebricks.client.transport.Json;
+import com.google.sitebricks.conversion.Converter;
+import com.google.sitebricks.conversion.ConverterRegistry;
+import com.google.sitebricks.conversion.StandardTypeConverter;
+import com.google.sitebricks.example.RestfulWebServiceWithSubpaths2;
+import org.testng.annotations.Test;
+
+import static com.google.sitebricks.example.RestfulWebServiceWithSubpaths2.TOPLEVEL;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class RestfuWebServiceWithSubpaths2AcceptanceTest {
+
+ public void shouldServiceTopLevelDynamicPath() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath2/" + TOPLEVEL)
+ .transports(String.class)
+ .over(Json.class)
+ .get();
+
+ assert TOPLEVEL.equals(response.toString());
+ }
+
+ public void shouldServiceFirstLevelStaticPath() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath2/junk/subpath1")
+ .transports(String.class)
+ .over(Json.class)
+ .post("");
+
+ assert RestfulWebServiceWithSubpaths2.PATH_1.equals(response.toString()) : response.toString();
+ }
+
+ public void shouldServiceSameFirstLevelStaticPathWithPutMethod() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath2/junk/subpath1")
+ .transports(String.class)
+ .over(Json.class)
+ .put("");
+
+ assert RestfulWebServiceWithSubpaths2.PATH_1_PUT.equals(response.toString())
+ : response.toString();
+ }
+
+ public void shouldServiceSameFirstLevelStaticPathWithDeleteMethod() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath2/junk/subpath1")
+ .transports(String.class)
+ .over(Json.class)
+ .delete();
+
+ assert RestfulWebServiceWithSubpaths2.PATH_1_DELETE.equals(response.toString())
+ : response.toString();
+ }
+
+ public void shouldServiceTwoLevelDynamicPath() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath2/junk/more_junk")
+ .transports(String.class)
+ .over(Json.class)
+ .post("");
+
+ assert "junk_more_junk".equals(response.toString()) : response.toString();
+ }
+
+ public void shouldServiceTwoLevelDynamicPathWithDeleteMethod() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath2/junk/more_junk")
+ .transports(String.class)
+ .over(Json.class)
+ .delete();
+
+ assert "delete:junk_more_junk".equals(response.toString()) : response.toString();
+ }
+
+ public void shouldServiceThreeLevelDynamicPathWithDeleteMethod() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath2/junk/more_junk/most_junk")
+ .transports(String.class)
+ .over(Json.class)
+ .delete();
+
+ assert "delete:junk_more_junk_most_junk".equals(response.toString()) : response.toString();
+ }
+
+ public void shouldServiceThreeLevelDynamicPathWithPutMethod() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath2/junk/more_junk/most_junk")
+ .transports(String.class)
+ .over(Json.class)
+ .put("");
+
+ assert "put:junk_more_junk_most_junk".equals(response.toString()) : response.toString();
+ }
+
+ public void shouldServiceThreeLevelDynamicPathWithPostMethod() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath2/junk/more_junk/most_junk")
+ .transports(String.class)
+ .over(Json.class)
+ .post("");
+
+ assert "post:junk_more_junk_most_junk".equals(response.toString()) : response.toString();
+ }
+
+ public void shouldServiceThreeLevelDynamicPathWithGetMethod() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath2/junk/more_junk/most_junk")
+ .transports(String.class)
+ .over(Json.class)
+ .get();
+
+ assert "get:junk_more_junk_most_junk".equals(response.toString()) : response.toString();
+ }
+
+ private Injector createInjector() {
+ return Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bind(ConverterRegistry.class).toInstance(new StandardTypeConverter(
+ ImmutableSet.of()));
+ }
+ });
+ }
+//
+// public void shouldService4LevelMixedPathWithGetMethod() {
+// WebResponse response = createInjector()
+// .getInstance(Web.class)
+// .clientOf(AcceptanceTest.BASE_URL + "/superpath2/junk/more_junk/most_junk/4l")
+// .transports(String.class)
+// .over(Json.class)
+// .get();
+//
+// assert "4l:get:junk_more_junk_most_junk".equals(response.toString()) : response.toString();
+// }
+//
+// public void shouldService4LevelMixedPathWithPostMethod() {
+// WebResponse response = createInjector()
+// .getInstance(Web.class)
+// .clientOf(AcceptanceTest.BASE_URL + "/superpath2/junk/more_junk/most_junk/4l")
+// .transports(String.class)
+// .over(Json.class)
+// .post("");
+//
+// assert "4l:post:junk_more_junk_most_junk".equals(response.toString()) : response.toString();
+// }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithSubpathsAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithSubpathsAcceptanceTest.java
new file mode 100644
index 00000000..3752cf4a
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/RestfuWebServiceWithSubpathsAcceptanceTest.java
@@ -0,0 +1,112 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.client.Web;
+import com.google.sitebricks.client.WebResponse;
+import com.google.sitebricks.client.transport.Json;
+import com.google.sitebricks.conversion.Converter;
+import com.google.sitebricks.conversion.ConverterRegistry;
+import com.google.sitebricks.conversion.StandardTypeConverter;
+import com.google.sitebricks.example.RestfulWebServiceWithSubpaths;
+import org.testng.annotations.Test;
+
+import java.util.Date;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class RestfuWebServiceWithSubpathsAcceptanceTest {
+
+ public void shouldServiceTopLevelPath() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath")
+ .transports(String.class)
+ .over(Json.class)
+ .get();
+
+ assert RestfulWebServiceWithSubpaths.TOPLEVEL.equals(response.toString());
+ }
+
+ public void shouldServiceFirstSubPath() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath/subpath1")
+ .transports(String.class)
+ .over(Json.class)
+ .post("");
+
+ assert RestfulWebServiceWithSubpaths.PATH_1.equals(response.toString()) : response.toString();
+ }
+
+ public void shouldServiceSecondSubPath() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath/subpath2")
+ .transports(String.class)
+ .over(Json.class)
+ .post("");
+
+ assert RestfulWebServiceWithSubpaths.PATH_2.equals(response.toString()) : response.toString();
+ }
+
+ public void shouldServiceThirdSubPath() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath/subpath3")
+ .transports(String.class)
+ .over(Json.class)
+ .post("");
+
+ assert RestfulWebServiceWithSubpaths.PATH_3.equals(response.toString()) : response.toString();
+ }
+
+ public void shouldServiceVariableTwoLevelSubPath() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath/subpath1/a_thing")
+ .transports(String.class)
+ .over(Json.class)
+ .post("");
+
+ assert "a_thing".equals(response.toString()) : response.toString();
+ }
+
+ public void shouldServiceVariableThreeLevelSubPath() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath/subpath1/a_thing/another_thing")
+ .transports(String.class)
+ .over(Json.class)
+ .post("");
+
+ assert "a_thing_another_thing".equals(response.toString()) : response.toString();
+ }
+
+ public void shouldServiceVariableTwoLevelSubPath2() {
+ String aString = "aoskdoaksd" + new Date().hashCode();
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/superpath/subpath3/" + aString)
+ .transports(String.class)
+ .over(Json.class)
+ .post("");
+
+ // Should be reflected
+ assert aString.equals(response.toString()) : response.toString();
+ }
+
+ private Injector createInjector() {
+ return Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bind(ConverterRegistry.class).toInstance(new StandardTypeConverter(
+ ImmutableSet.of()));
+ }
+ });
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/SelectRoutingAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/SelectRoutingAcceptanceTest.java
new file mode 100644
index 00000000..0fd3e4e4
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/SelectRoutingAcceptanceTest.java
@@ -0,0 +1,254 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.sitebricks.acceptance.page.SelectRoutingPage;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.openqa.selenium.WebDriver;
+import org.testng.annotations.Test;
+
+@Test(suiteName = AcceptanceTest.SUITE)
+public class SelectRoutingAcceptanceTest {
+
+ public void shouldRenderDivForDefaultGetOnly() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForDefaultGetOnly");
+ assert page.hasExpectedDiv("defaultGet");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForFooGetOnly() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForFooGetOnly");
+ assert page.hasExpectedDiv("fooGet");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForBarGetOnly() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForBarGetOnly");
+ assert page.hasExpectedDiv("barGet");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForFooBarGet() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForFooBarGet");
+
+ assert page.hasExpectedDiv("fooGet");
+ assert page.hasExpectedDiv("barGet");
+ assert page.hasExpectedDivCount(2);
+ }
+
+ public void shouldRenderDivForUnknownGet() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForUnknownGet");
+ assert page.hasExpectedDiv("defaultGet");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForUnknownAndFooGet() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForUnknownAndFooGet");
+ assert page.hasExpectedDiv("fooGet");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForRedirectGet() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForRedirectGet");
+ assert page.hasExpectedDiv("defaultGet");
+ assert page.hasExpectedDiv("redirectGet");
+ assert page.hasExpectedDivCount(2);
+ }
+
+ public void shouldRenderDivForDefaultPostOnly() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForDefaultPostOnly");
+ assert page.hasExpectedDiv("defaultPost");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForFooPostOnly() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForFooPostOnly");
+ assert page.hasExpectedDiv("fooPost");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForBarPostOnly() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForBarPostOnly");
+ assert page.hasExpectedDiv("barPost");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForFooBarPost() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForFooBarPost");
+
+ assert page.hasExpectedDiv("fooPost");
+ assert page.hasExpectedDiv("barPost");
+ assert page.hasExpectedDivCount(2);
+ }
+
+ public void shouldRenderDivForUnknownPost() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForUnknownPost");
+ assert page.hasExpectedDiv("defaultPost");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForUnknownAndFooPost() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForUnknownAndFooPost");
+ assert page.hasExpectedDiv("fooPost");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForRedirectPost() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForRedirectPost");
+ assert page.hasExpectedDiv("defaultGet");
+ assert page.hasExpectedDiv("redirectPost");
+ assert page.hasExpectedDivCount(2);
+ }
+
+
+ public void shouldRenderDivForDefaultPutOnly() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForDefaultPutOnly");
+ assert page.hasExpectedDiv("defaultPut");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForFooPutOnly() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForFooPutOnly");
+ assert page.hasExpectedDiv("fooPut");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForBarPutOnly() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForBarPutOnly");
+ assert page.hasExpectedDiv("barPut");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForFooBarPut() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForFooBarPut");
+
+ assert page.hasExpectedDiv("fooPut");
+ assert page.hasExpectedDiv("barPut");
+ assert page.hasExpectedDivCount(2);
+ }
+
+ public void shouldRenderDivForUnknownPut() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForUnknownPut");
+ assert page.hasExpectedDiv("defaultPut");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForUnknownAndFooPut() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForUnknownAndFooPut");
+ assert page.hasExpectedDiv("fooPut");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForRedirectPut() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForRedirectPut");
+ assert page.hasExpectedDiv("defaultGet");
+ assert page.hasExpectedDiv("redirectPut");
+ assert page.hasExpectedDivCount(2);
+ }
+
+
+ public void shouldRenderDivForDefaultDeleteOnly() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForDefaultDeleteOnly");
+ assert page.hasExpectedDiv("defaultDelete");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForFooDeleteOnly() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForFooDeleteOnly");
+ assert page.hasExpectedDiv("fooDelete");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForBarDeleteOnly() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForBarDeleteOnly");
+ assert page.hasExpectedDiv("barDelete");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForFooBarDelete() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForFooBarDelete");
+
+ assert page.hasExpectedDiv("fooDelete");
+ assert page.hasExpectedDiv("barDelete");
+ assert page.hasExpectedDivCount(2);
+ }
+
+ public void shouldRenderDivForUnknownDelete() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForUnknownDelete");
+ assert page.hasExpectedDiv("defaultDelete");
+ assert page.hasExpectedDivCount(1);
+ }
+
+
+ public void shouldRenderDivForUnknownAndFooDelete() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForUnknownAndFooDelete");
+ assert page.hasExpectedDiv("fooDelete");
+ assert page.hasExpectedDivCount(1);
+ }
+
+ public void shouldRenderDivForRedirectDelete() {
+ SelectRoutingPage page = loadPage();
+
+ page.submit("shouldRenderDivForRedirectDelete");
+ assert page.hasExpectedDiv("defaultGet");
+ assert page.hasExpectedDiv("redirectDelete");
+ assert page.hasExpectedDivCount(2);
+ }
+
+ private SelectRoutingPage loadPage() {
+ WebDriver driver = AcceptanceTest.createWebDriver();
+ return SelectRoutingPage.open(driver);
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/SpiRestfuWebServiceWithSubpaths2AcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/SpiRestfuWebServiceWithSubpaths2AcceptanceTest.java
new file mode 100644
index 00000000..b391250a
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/SpiRestfuWebServiceWithSubpaths2AcceptanceTest.java
@@ -0,0 +1,52 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.client.Web;
+import com.google.sitebricks.client.WebResponse;
+import com.google.sitebricks.client.transport.Json;
+import com.google.sitebricks.conversion.Converter;
+import com.google.sitebricks.conversion.ConverterRegistry;
+import com.google.sitebricks.conversion.StandardTypeConverter;
+import org.testng.annotations.Test;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class SpiRestfuWebServiceWithSubpaths2AcceptanceTest {
+
+ public void shouldServiceTopLevelDynamicPath() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/spi/test")
+ .transports(String.class)
+ .over(Json.class)
+ .get();
+
+ assert "get:top".equals(response.toString());
+ }
+
+ public void shouldServiceFirstLevelStaticPath() {
+ WebResponse response = createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/spi/test")
+ .transports(String.class)
+ .over(Json.class)
+ .post("");
+
+ assert "post:junk_subpath1".equals(response.toString()) : response.toString();
+ }
+
+ private Injector createInjector() {
+ return Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bind(ConverterRegistry.class).toInstance(new StandardTypeConverter(
+ ImmutableSet.of()));
+ }
+ });
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/StatsAcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/StatsAcceptanceTest.java
new file mode 100644
index 00000000..922b37c6
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/StatsAcceptanceTest.java
@@ -0,0 +1,24 @@
+package com.google.sitebricks.acceptance;
+
+import com.google.sitebricks.acceptance.page.StatsPage;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.openqa.selenium.WebDriver;
+import org.testng.annotations.Test;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@Test(suiteName = AcceptanceTest.SUITE)
+public class StatsAcceptanceTest {
+
+ public void shouldRenderStatsForPageRequest() {
+ WebDriver driver = AcceptanceTest.createWebDriver();
+
+ // Request start page.
+ driver.get(AcceptanceTest.BASE_URL);
+
+ StatsPage statsPage = StatsPage.open(driver);
+
+ assert statsPage.hasNonZeroStats() : "Recorded stats should be at least 1";
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/CasePage.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/CasePage.java
new file mode 100644
index 00000000..b1885f77
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/CasePage.java
@@ -0,0 +1,29 @@
+package com.google.sitebricks.acceptance.page;
+
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.openqa.selenium.support.How;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.PageFactory;
+
+public class CasePage {
+
+ @FindBy(how = How.XPATH, using = "//div[@class='entry']/font[@color='green']")
+ private WebElement entry;
+
+ private WebDriver driver;
+
+ public CasePage(WebDriver driver) {
+ this.driver = driver;
+ }
+
+ public String getDisplayedColor() {
+ return entry.getText();
+ }
+
+ public static CasePage open(WebDriver driver) {
+ driver.get(AcceptanceTest.BASE_URL + "/case");
+ return PageFactory.initElements(driver, CasePage.class);
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/ConnegPage.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/ConnegPage.java
new file mode 100644
index 00000000..8e9ade95
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/ConnegPage.java
@@ -0,0 +1,45 @@
+package com.google.sitebricks.acceptance.page;
+
+import com.google.inject.Guice;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.client.Web;
+import com.google.sitebricks.client.transport.Text;
+import org.dom4j.DocumentException;
+import org.dom4j.DocumentHelper;
+
+import java.util.Map;
+
+/**
+ * Page object that wraps the content negotiation page.
+ *
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+public class ConnegPage {
+ private String content;
+
+ private ConnegPage(String content) {
+ try {
+ this.content = DocumentHelper.parseText(content)
+ .selectSingleNode("//div['content'][1]")
+ .getText();
+ } catch (DocumentException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public boolean hasContent(String content) {
+ return this.content.trim().equals(content);
+ }
+
+ public static ConnegPage openWithHeaders(Map headers) {
+ String content = Guice.createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/conneg", headers)
+ .transports(String.class)
+ .over(Text.class)
+ .get()
+ .toString();
+
+ return new ConnegPage(content);
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/ConversionPage.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/ConversionPage.java
new file mode 100644
index 00000000..5c314041
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/ConversionPage.java
@@ -0,0 +1,95 @@
+package com.google.sitebricks.acceptance.page;
+
+
+import java.net.URLEncoder;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.PageFactory;
+
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.example.SitebricksConfig;
+
+public class ConversionPage {
+ private WebDriver driver;
+
+ public ConversionPage(WebDriver driver) {
+ this.driver = driver;
+ }
+
+ public boolean hasDate(Date date) {
+ SimpleDateFormat sdf = new SimpleDateFormat(SitebricksConfig.DEFAULT_DATE_TIME_FORMAT);
+ String target = sdf.format(date);
+
+ return driver.findElement(By.id("boundDate"))
+ .getText()
+ .contains(target);
+ }
+
+ public boolean hasCalendar(Calendar calendar) {
+ SimpleDateFormat sdf = new SimpleDateFormat(SitebricksConfig.DEFAULT_DATE_TIME_FORMAT);
+ String target = sdf.format(calendar.getTime());
+
+ String node = driver.getPageSource();
+ return driver.findElement(By.id("boundCalendar"))
+ .getText()
+ .contains(target);
+ }
+
+ public boolean hasMessage(String message) {
+ return driver.findElement(By.id("boundText"))
+ .getText()
+ .contains(message);
+ }
+
+ public boolean hasDouble(Double dbl) {
+ return driver.findElement(By.id("boundDouble"))
+ .getText()
+ .contains(dbl.toString());
+ }
+
+ public static ConversionPage open(WebDriver driver, Date date, Calendar calendar, String dateFormat, String msg, Double dbl) {
+ SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
+ StringBuilder sb = new StringBuilder ();
+
+ if (date != null) {
+ if (sb.length() > 0)
+ sb.append("&");
+ sb.append ("date=").append(encode(sdf.format(date)));
+ }
+
+ if (calendar != null) {
+ if (sb.length() > 0)
+ sb.append("&");
+ sb.append ("calendar=").append(encode(sdf.format(calendar.getTime())));
+ }
+
+ if (msg != null) {
+ if (sb.length() > 0)
+ sb.append("&");
+ sb.append ("message=").append(encode(msg));
+ }
+
+ if (msg != null) {
+ if (sb.length() > 0)
+ sb.append("&");
+ sb.append ("dbl=").append(encode(dbl.toString()));
+ }
+
+ sb.insert(0,"/conversion?").insert(0, AcceptanceTest.BASE_URL);
+ driver.get(sb.toString());
+ return PageFactory.initElements(driver, ConversionPage.class);
+ }
+
+ private static String encode(String s){
+ try {
+ return URLEncoder.encode(s,"UTF-8");
+ }
+ catch(Exception e){
+ return s;
+ }
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/DecoratorPage.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/DecoratorPage.java
new file mode 100644
index 00000000..723e3501
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/DecoratorPage.java
@@ -0,0 +1,36 @@
+package com.google.sitebricks.acceptance.page;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.PageFactory;
+
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+
+public class DecoratorPage {
+
+ private WebDriver driver;
+
+ public DecoratorPage(WebDriver driver) {
+ this.driver = driver;
+ }
+ public boolean hasBasePageText() {
+ return driver.getPageSource().contains("Text defined in");
+ }
+ public boolean hasBasePageVariable() {
+ return driver.getPageSource().contains("from the superclass");
+ }
+ public boolean hasSubclassVariableInTemplate() {
+ return driver.getPageSource().contains("This comes from the subclass");
+ }
+ public boolean hasSubclassVariable() {
+ return driver.getPageSource().contains("very cool");
+ }
+
+ public boolean hasSubclassText() {
+ return driver.getPageSource().contains("This is in the extension");
+ }
+
+ public static DecoratorPage open(WebDriver driver) {
+ driver.get(AcceptanceTest.BASE_URL + "/template");
+ return PageFactory.initElements(driver, DecoratorPage.class);
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/DynamicJsPage.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/DynamicJsPage.java
new file mode 100644
index 00000000..e1b7d70b
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/DynamicJsPage.java
@@ -0,0 +1,24 @@
+package com.google.sitebricks.acceptance.page;
+
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.example.DynamicJs;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.PageFactory;
+
+public class DynamicJsPage {
+
+ private WebDriver driver;
+
+ public DynamicJsPage(WebDriver driver) {
+ this.driver = driver;
+ }
+
+ public boolean hasDynamicText() {
+ return driver.getPageSource().contains(DynamicJs.A_MESSAGE);
+ }
+
+ public static DynamicJsPage open(WebDriver driver) {
+ driver.get(AcceptanceTest.BASE_URL + "/dynamic.js");
+ return PageFactory.initElements(driver, DynamicJsPage.class);
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/EmbedPage.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/EmbedPage.java
new file mode 100644
index 00000000..f4a53cc9
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/EmbedPage.java
@@ -0,0 +1,32 @@
+package com.google.sitebricks.acceptance.page;
+
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.PageFactory;
+
+public class EmbedPage {
+ private static final String EMBED_MSG = "Message : " +
+ "Embedding in google-sitebricks is awesome!";
+
+ private WebDriver driver;
+
+ public EmbedPage(WebDriver driver) {
+ this.driver = driver;
+ }
+
+ public boolean hasCssLink() {
+
+ // UGH, but webdriver is sucking it up with xpath soo....
+ return driver.getPageSource().contains(" autobotTextFields = driver.findElements(By.name("autobots"));
+
+ autobotTextFields.get(0).sendKeys(s1);
+ autobotTextFields.get(1).sendKeys(s2);
+ autobotTextFields.get(2).sendKeys(s3);
+ }
+
+ public boolean hasBoundAutobots(String expected) {
+ return driver.findElement(By.id("boundAutobots"))
+ .getText()
+ .contains(expected);
+
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/HelloWorldPage.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/HelloWorldPage.java
new file mode 100644
index 00000000..bf04db98
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/HelloWorldPage.java
@@ -0,0 +1,38 @@
+package com.google.sitebricks.acceptance.page;
+
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.example.HelloWorld;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.PageFactory;
+
+public class HelloWorldPage {
+
+ private WebDriver driver;
+
+ public HelloWorldPage(WebDriver driver) {
+ this.driver = driver;
+ }
+
+ public boolean hasHelloWorldMessage() {
+ //TODO ugh! stupid xpath doesn't work =(
+ return driver.getPageSource().contains(HelloWorld.HELLO_MSG);
+ }
+
+ public boolean hasCorrectDoctype() {
+ return driver.getPageSource().startsWith("");
+ }
+
+ public boolean hasMangledString() {
+ return driver.getPageSource().contains(new HelloWorld().mangle(HelloWorld.HELLO_MSG));
+ }
+
+ public static HelloWorldPage open(WebDriver driver, String url) {
+ driver.get(AcceptanceTest.BASE_URL + url);
+ return PageFactory.initElements(driver, HelloWorldPage.class);
+ }
+
+ public boolean hasNonSelfClosingScriptTag() {
+ return driver.getPageSource().contains("");
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/HiddenFieldMethodPage.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/HiddenFieldMethodPage.java
new file mode 100644
index 00000000..e43ad7cf
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/HiddenFieldMethodPage.java
@@ -0,0 +1,42 @@
+package com.google.sitebricks.acceptance.page;
+
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.PageFactory;
+import org.testng.annotations.Test;
+
+/**
+ * @author Peter Knego
+ */
+public class HiddenFieldMethodPage {
+
+ private WebDriver driver;
+
+ public HiddenFieldMethodPage(WebDriver driver) {
+ this.driver = driver;
+ }
+
+ // "clicks" the submit button
+ public void submitPut() {
+ driver.findElement(By.id("put"))
+ .submit();
+ }
+
+ public void enterText(String text) {
+ driver.findElement(By.name("text"))
+ .sendKeys(text);
+ }
+
+ public boolean isPutMessage() {
+ return driver.findElement(By.id("putMessage"))
+ .getText()
+ .endsWith("PUT");
+ }
+
+ public static HiddenFieldMethodPage open(WebDriver driver) {
+ driver.get(AcceptanceTest.BASE_URL + "/hiddenfieldmethod");
+ return PageFactory.initElements(driver, HiddenFieldMethodPage.class);
+ }
+
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/I18nPage.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/I18nPage.java
new file mode 100644
index 00000000..197a9b10
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/I18nPage.java
@@ -0,0 +1,45 @@
+package com.google.sitebricks.acceptance.page;
+
+import com.google.inject.Guice;
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.client.Web;
+import com.google.sitebricks.client.transport.Text;
+import org.dom4j.DocumentException;
+import org.dom4j.DocumentHelper;
+
+import java.util.Map;
+
+public class I18nPage {
+ private final String content;
+ public static final String NAME = "Dhanji";
+
+ public I18nPage(String content) {
+ try {
+ this.content = DocumentHelper.parseText(content)
+ .selectSingleNode("//span['localizedMessage'][1]")
+ .getText();
+ } catch (DocumentException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public boolean hasHelloTo(String name) {
+ return content.trim().equals("Hello there " + name + "!");
+ }
+
+ public boolean hasBonjourTo(String name) {
+ return content.trim().equals("Bonjour misieu " + name + "!");
+ }
+
+ public static I18nPage openWithHeaders(Map headers) {
+ String content = Guice.createInjector()
+ .getInstance(Web.class)
+ .clientOf(AcceptanceTest.BASE_URL + "/i18n?name=" + NAME, headers)
+ .transports(String.class)
+ .over(Text.class)
+ .get()
+ .toString();
+
+ return new I18nPage(content);
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/PageChainPage.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/PageChainPage.java
new file mode 100644
index 00000000..18010422
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/PageChainPage.java
@@ -0,0 +1,31 @@
+package com.google.sitebricks.acceptance.page;
+
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.PageFactory;
+
+public class PageChainPage {
+
+ private WebDriver driver;
+
+ public PageChainPage(WebDriver driver) {
+ this.driver = driver;
+ }
+
+
+ public static PageChainPage open(WebDriver driver) {
+ driver.get(AcceptanceTest.BASE_URL + "/pagechain");
+ return PageFactory.initElements(driver, PageChainPage.class);
+ }
+
+ public void enterText(String someText) {
+ driver.findElement(By.name("userValue"))
+ .sendKeys(someText);
+ }
+
+ public void next() {
+ driver.findElement(By.id("send"))
+ .submit();
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/RepeatPage.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/RepeatPage.java
new file mode 100644
index 00000000..fa343352
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/RepeatPage.java
@@ -0,0 +1,49 @@
+package com.google.sitebricks.acceptance.page;
+
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import org.openqa.selenium.By;
+import org.openqa.selenium.support.How;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.PageFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RepeatPage {
+ @FindBy(how = How.XPATH, using = "//div[@class='entry'][1]/ul")
+ private WebElement namesEntry;
+
+ @FindBy(how = How.XPATH, using = "//div[@class='entry'][2]/ul")
+ private WebElement moviesEntry;
+
+ private WebDriver driver;
+
+ public RepeatPage(WebDriver driver) {
+ this.driver = driver;
+ }
+
+ public List getRepeatedNames() {
+ List items = new ArrayList();
+ for (WebElement li : namesEntry.findElements(By.tagName("li"))) {
+ items.add(li.getText().trim());
+ }
+
+ return items;
+ }
+
+ public List getRepeatedMovies() {
+ List items = new ArrayList();
+ for (WebElement li : moviesEntry.findElements(By.tagName("li"))) {
+ items.add(li.getText());
+ }
+
+ return items;
+ }
+
+ public static RepeatPage open(WebDriver driver) {
+ driver.get(AcceptanceTest.BASE_URL + "/repeat");
+ return PageFactory.initElements(driver, RepeatPage.class);
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/SelectRoutingPage.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/SelectRoutingPage.java
new file mode 100644
index 00000000..4afc6746
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/SelectRoutingPage.java
@@ -0,0 +1,44 @@
+package com.google.sitebricks.acceptance.page;
+
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.example.HelloWorld;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.PageFactory;
+
+import java.util.List;
+
+public class SelectRoutingPage {
+
+ private WebDriver driver;
+
+ public SelectRoutingPage(WebDriver driver) {
+ this.driver = driver;
+ }
+
+ public boolean hasExpectedDiv(String className) {
+ try {
+ WebElement element = driver.findElement(By.className(className));
+ } catch (Exception e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public boolean hasExpectedDivCount(int i) {
+ List elements = driver.findElements(By.className("result"));
+ return elements.size() == i;
+ }
+
+ public static SelectRoutingPage open(WebDriver driver) {
+ driver.get(AcceptanceTest.BASE_URL + "/select");
+ return PageFactory.initElements(driver, SelectRoutingPage.class);
+ }
+
+ public void submit(String s) {
+ driver.findElement(By.id(s + "Submit")).submit();
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/StatsPage.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/StatsPage.java
new file mode 100644
index 00000000..2e7bb3d5
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/page/StatsPage.java
@@ -0,0 +1,32 @@
+package com.google.sitebricks.acceptance.page;
+
+import com.google.sitebricks.acceptance.util.AcceptanceTest;
+import com.google.sitebricks.example.Start;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.PageFactory;
+
+public class StatsPage {
+ private WebDriver driver;
+
+ public StatsPage(WebDriver driver) {
+ this.driver = driver;
+ }
+
+ public boolean hasNonZeroStats() {
+ String pageSource = driver.getPageSource();
+ int pageLoadsStart = pageSource.indexOf(Start.PAGE_LOADS);
+ // the format is as follows: label: value
+ pageLoadsStart += Start.PAGE_LOADS.length();
+ pageLoadsStart += ":".length();
+
+ int value = Integer.parseInt(
+ pageSource.substring(pageLoadsStart, pageSource.indexOf(" ")).trim());
+
+ return value > 0;
+ }
+
+ public static StatsPage open(WebDriver driver) {
+ driver.get(AcceptanceTest.BASE_URL + "/stats");
+ return PageFactory.initElements(driver, StatsPage.class);
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/util/AcceptanceTest.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/util/AcceptanceTest.java
new file mode 100644
index 00000000..182b29fd
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/util/AcceptanceTest.java
@@ -0,0 +1,16 @@
+package com.google.sitebricks.acceptance.util;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.htmlunit.HtmlUnitDriver;
+
+/**
+ * @author Tom Wilson (tom@tomwilson.name)
+ */
+public class AcceptanceTest {
+ public static final String SUITE = "acceptance";
+ public static final String BASE_URL = "http://localhost:4040/sitebricks";
+
+ public static WebDriver createWebDriver() {
+ return new HtmlUnitDriver();
+ }
+}
diff --git a/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/util/Jetty.java b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/util/Jetty.java
new file mode 100644
index 00000000..73a6efc5
--- /dev/null
+++ b/sitebricks-acceptance-tests/src/test/java/com/google/sitebricks/acceptance/util/Jetty.java
@@ -0,0 +1,45 @@
+package com.google.sitebricks.acceptance.util;
+
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.webapp.WebAppContext;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+public class Jetty {
+ private static final String APP_NAME = "/sitebricks";
+ private static final int PORT = 4040;
+
+ private final Server server;
+
+ public Jetty() {
+ this(new WebAppContext("src/main/resources", APP_NAME), PORT);
+ }
+
+ public Jetty(String path) {
+ this(new WebAppContext(path, APP_NAME), PORT);
+ }
+
+ public Jetty(WebAppContext webAppContext, int port) {
+ server = new Server(port);
+ server.addHandler(webAppContext);
+ }
+
+ public void start() throws Exception {
+ server.start();
+ }
+
+ public void join() throws Exception {
+ server.join();
+ }
+
+ public void stop() throws Exception {
+ server.stop();
+ }
+
+ public static void main(String... args) throws Exception {
+ Jetty jetty = new Jetty();
+ jetty.start();
+ jetty.join();
+ }
+}
diff --git a/sitebricks-client/pom.xml b/sitebricks-client/pom.xml
new file mode 100644
index 00000000..9db9dda8
--- /dev/null
+++ b/sitebricks-client/pom.xml
@@ -0,0 +1,60 @@
+
+ 4.0.0
+
+ com.google.sitebricks
+ sitebricks-parent
+ 0.8.5
+
+ sitebricks-client
+ Sitebricks :: Client
+
+
+
+ org.testng
+ testng
+ ${org.testng.version}
+ jdk15
+ test
+
+
+ com.google.sitebricks
+ sitebricks-converter
+
+
+ net.jcip
+ jcip-annotations
+
+
+ com.google.inject
+ guice
+
+
+ com.google.inject.extensions
+ guice-servlet
+
+
+ com.ning
+ async-http-client
+
+
+ commons-io
+ commons-io
+
+
+ javax.servlet
+ servlet-api
+
+
+ com.thoughtworks.xstream
+ xstream
+
+
+ org.codehaus.jackson
+ jackson-core-asl
+
+
+ org.codehaus.jackson
+ jackson-mapper-asl
+
+
+
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/AHCWebClient.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/AHCWebClient.java
new file mode 100644
index 00000000..446c41cd
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/AHCWebClient.java
@@ -0,0 +1,152 @@
+package com.google.sitebricks.client;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.ning.http.client.AsyncHttpClient;
+import com.ning.http.client.AsyncHttpClientConfig;
+import com.ning.http.client.Realm;
+import com.ning.http.client.RequestBuilder;
+import com.ning.http.client.Response;
+import net.jcip.annotations.ThreadSafe;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * @author Jeanfrancois Arcand (jfarcand@apache.org)
+ */
+@ThreadSafe
+ //@Concurrent
+class AHCWebClient implements WebClient {
+ private final Injector injector;
+ private final String url;
+ private final Map headers;
+ private final Class transporting;
+ private final Key extends Transport> transport;
+ private final AsyncHttpClient httpClient;
+
+ private final Web.Auth authType;
+ private final String username;
+ private final String password;
+
+ public AHCWebClient(Injector injector, Web.Auth authType, String username,
+ String password, String url, Map headers,
+ Class transporting,
+ Key extends Transport> transport) {
+
+ this.injector = injector;
+
+ this.url = url;
+ this.headers = (null == headers) ? null : ImmutableMap.copyOf(headers);
+
+
+ this.authType = authType;
+ this.username = username;
+ this.password = password;
+ this.transporting = transporting;
+ this.transport = transport;
+
+ // configure auth
+ AsyncHttpClientConfig.Builder c = new AsyncHttpClientConfig.Builder();
+ if (null != authType) {
+ Realm.RealmBuilder b = new Realm.RealmBuilder();
+ // TODO: Add support for Kerberos and SPNEGO
+ Realm.AuthScheme scheme = authType.equals(Web.Auth.BASIC) ? Realm.AuthScheme.BASIC : Realm.AuthScheme.DIGEST;
+ b.setPrincipal(username).setPassword(password).setScheme(scheme);
+ c.setRealm(b.build());
+ }
+
+ this.httpClient = new AsyncHttpClient(c.build());
+
+ }
+
+ private static URI toUri(String url) {
+ try {
+ return new URI(url);
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private WebResponse simpleRequest(RequestBuilder requestBuilder) {
+
+ //set request headers as necessary
+ if (null != headers)
+ for (Map.Entry header : headers.entrySet())
+ requestBuilder.addHeader(header.getKey(), header.getValue());
+
+ try {
+
+ Response r = httpClient.executeRequest(requestBuilder.build()).get();
+
+ return new WebResponseImpl(injector, r);
+ } catch (IOException e) {
+ throw new TransportException(e);
+ } catch (InterruptedException e) {
+ throw new TransportException(e);
+ } catch (ExecutionException e) {
+ throw new TransportException(e);
+ }
+ }
+
+
+ private WebResponse request(RequestBuilder requestBuilder, T t) {
+
+ //set request headers as necessary
+ if (null != headers)
+ for (Map.Entry header : headers.entrySet())
+ requestBuilder.addHeader(header.getKey(), header.getValue());
+
+ //fire method
+ try {
+
+ // Read the entity from the transport plugin.
+ final ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ injector.getInstance(transport)
+ .out(stream, transporting, t);
+
+ // TODO worry about endian issues? Or will Content-Encoding be sufficient?
+ // OOM if the stream is too bug
+ final byte[] outBuffer = stream.toByteArray();
+
+ //set request body
+ requestBuilder.setBody(outBuffer);
+
+ Response r = httpClient.executeRequest(requestBuilder.build()).get();
+
+ return new WebResponseImpl(injector, r);
+ } catch (IOException e) {
+ throw new TransportException(e);
+ } catch (InterruptedException e) {
+ throw new TransportException(e);
+ } catch (ExecutionException e) {
+ throw new TransportException(e);
+ }
+ }
+
+ public WebResponse get() {
+ return simpleRequest((new RequestBuilder("GET")).setUrl(url));
+ }
+
+ public WebResponse post(T t) {
+ return request((new RequestBuilder("POST")).setUrl(url), t);
+ }
+
+ public WebResponse put(T t) {
+ return request((new RequestBuilder("PUT")).setUrl(url), t);
+ }
+
+ public WebResponse delete() {
+ return simpleRequest((new RequestBuilder("DELETE")).setUrl(url));
+ }
+
+ @Override
+ public void close() {
+ httpClient.close();
+ }
+}
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/CommonsWeb.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/CommonsWeb.java
new file mode 100644
index 00000000..3de347e2
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/CommonsWeb.java
@@ -0,0 +1,28 @@
+package com.google.sitebricks.client;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import net.jcip.annotations.Immutable;
+
+import java.util.Map;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+*/
+@Immutable
+class CommonsWeb implements Web {
+ private final Provider builder;
+
+ @Inject
+ public CommonsWeb(Provider builder) {
+ this.builder = builder;
+ }
+
+ public FormatBuilder clientOf(String url) {
+ return builder.get().clientOf(url, null);
+ }
+
+ public FormatBuilder clientOf(String url, Map headers) {
+ return builder.get().clientOf(url, headers);
+ }
+}
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/Transport.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/Transport.java
new file mode 100644
index 00000000..7186e48e
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/Transport.java
@@ -0,0 +1,48 @@
+package com.google.sitebricks.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+public interface Transport {
+ /**
+ * Reads from a given inputstream and returns an object representing the unmarshalled
+ * form of the underlying protocol data.
+ *
+ * @param in An inputstream to read data from. This stream will NOT be closed
+ * by the implementation of this interface.
+ * @param type The type to read in. The method will return an instance of this
+ * type representing the data in the {@code in} stream.
+ * @return An instance of {@code type} representing the data in the provided
+ * stream.
+ *
+ * @throws IOException Thrown if there is an error reading from this stream.
+ */
+ T in(InputStream in, Class type) throws IOException;
+
+ /**
+ * Converts a given object into transportable data and writes it to the provided
+ * OutputStream.
+ *
+ * @param out An open outputstream to write to. This stream will NOT be closed.
+ * @param type The type of the data being serialized to the stream.
+ * @param data An object representing the data to be written out.
+
+ * @throws IOException Thrown if there is an error writing to this stream.
+ */
+ void out(OutputStream out, Class type, T data) throws IOException;
+
+ /**
+ * Returns the HTTP content type marshalled by this transport. For example, the
+ * {@link com.google.sitebricks.client.transport.Text} transport transforms plain
+ * strings to and from the HTTP stream. Its content type is {@code text/plain}. This
+ * is only a default (or suggested) content type. You may of course, return whatever
+ * content type is most suitable if using this transport to deliver web responses.
+ *
+ * @return A non-empty string representing a HTTP content (mime) type.
+ */
+ String contentType();
+}
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/TransportException.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/TransportException.java
new file mode 100644
index 00000000..72209a29
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/TransportException.java
@@ -0,0 +1,10 @@
+package com.google.sitebricks.client;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+public final class TransportException extends RuntimeException {
+ public TransportException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/Web.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/Web.java
new file mode 100644
index 00000000..c68eb744
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/Web.java
@@ -0,0 +1,29 @@
+package com.google.sitebricks.client;
+
+import com.google.inject.ImplementedBy;
+
+import java.util.Map;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@ImplementedBy(CommonsWeb.class)
+public interface Web {
+ enum Auth {
+ BASIC, DIGEST
+ }
+
+ FormatBuilder clientOf(String url);
+
+ FormatBuilder clientOf(String url, Map headers);
+
+ static interface FormatBuilder {
+ ReadAsBuilder transports(Class clazz);
+
+ FormatBuilder auth(Auth auth, String username, String password);
+ }
+
+ static interface ReadAsBuilder {
+ WebClient over(Class extends Transport> clazz);
+ }
+}
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/WebClient.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/WebClient.java
new file mode 100644
index 00000000..12616e08
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/WebClient.java
@@ -0,0 +1,19 @@
+package com.google.sitebricks.client;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+public interface WebClient {
+ WebResponse get();
+
+ WebResponse post(T t);
+
+ WebResponse put(T t);
+
+ WebResponse delete();
+
+ /**
+ * Close the underlying client.
+ */
+ void close();
+}
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/WebClientBuilder.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/WebClientBuilder.java
new file mode 100644
index 00000000..d215e7fc
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/WebClientBuilder.java
@@ -0,0 +1,72 @@
+package com.google.sitebricks.client;
+
+import com.google.common.base.Preconditions;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import net.jcip.annotations.NotThreadSafe;
+
+import java.util.Map;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+@NotThreadSafe
+class WebClientBuilder implements Web.FormatBuilder {
+
+ private final Injector injector;
+
+ private String url;
+ private Map headers;
+
+ private Web.Auth authType;
+ private String username;
+ private String password;
+
+ @Inject
+ public WebClientBuilder(Injector injector) {
+ this.injector = injector;
+ }
+
+ public Web.FormatBuilder clientOf(String url) {
+ this.url = url;
+ this.headers = null;
+
+ return this;
+ }
+
+ public Web.FormatBuilder clientOf(String url, Map headers) {
+ this.url = url;
+ this.headers = headers;
+
+ return this;
+ }
+
+ public Web.ReadAsBuilder transports(Class clazz) {
+ return new InternalReadAsBuilder(clazz);
+ }
+
+ public Web.FormatBuilder auth(Web.Auth auth, String username, String password) {
+ Preconditions.checkArgument(null != auth, "Invalid auth type, null.");
+ Preconditions.checkArgument(null != username, "Username cannot be null.");
+ Preconditions.checkArgument(null != password, "Password cannot be null.");
+
+ this.authType = auth;
+ this.username = username;
+ this.password = password;
+ return this;
+ }
+
+ private class InternalReadAsBuilder implements Web.ReadAsBuilder {
+ private final Class transporting;
+
+ private InternalReadAsBuilder(Class transporting) {
+ this.transporting = transporting;
+ }
+
+ public WebClient over(Class extends Transport> transport) {
+ return new AHCWebClient(injector, authType, username, password, url,
+ headers, transporting, Key.get(transport));
+ }
+ }
+}
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/WebResponse.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/WebResponse.java
new file mode 100644
index 00000000..01e5d991
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/WebResponse.java
@@ -0,0 +1,20 @@
+package com.google.sitebricks.client;
+
+import java.util.Map;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+public interface WebResponse {
+ Map getHeaders();
+
+ int status();
+
+ ResponseTransportBuilder to(Class data);
+
+ String toString();
+
+ public static interface ResponseTransportBuilder {
+ T using(Class extends Transport> transport);
+ }
+}
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/WebResponseImpl.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/WebResponseImpl.java
new file mode 100644
index 00000000..a4187a6d
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/WebResponseImpl.java
@@ -0,0 +1,89 @@
+package com.google.sitebricks.client;
+
+import com.google.inject.Injector;
+import com.ning.http.client.Response;
+import net.jcip.annotations.NotThreadSafe;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ * @author Jeanfrancois Arcand (jfarcand@apache.org)
+ */
+@NotThreadSafe
+class WebResponseImpl implements WebResponse {
+ private final Injector injector;
+ private final Response response;
+
+ //memo field
+ private Map headers;
+
+ public WebResponseImpl(Injector injector, Response response) {
+ this.injector = injector;
+ this.response = response;
+ }
+
+ public Map getHeaders() {
+ if (null != this.headers)
+ return this.headers;
+
+ //translate from ahc http client headers
+ final Map headers = new HashMap();
+ for (Map.Entry> header : response.getHeaders().entrySet()) {
+ for (String value : header.getValue()) {
+ headers.put(header.getKey(), value);
+ }
+ }
+
+ return this.headers = headers;
+ }
+
+ public ResponseTransportBuilder to(final Class data) {
+ return new ResponseTransportBuilder() {
+
+ public T using(Class extends Transport> transport) {
+
+ InputStream in = null;
+ try {
+ in = response.getResponseBodyAsStream();
+ return injector
+ .getInstance(transport)
+ .in(in, data);
+
+ } catch (IOException e) {
+ throw new TransportException(e);
+
+ //ugly stream closing here, to abstract it away from user code
+ } finally {
+ try {
+ if (null != in)
+ in.close();
+ } catch (IOException e) {
+ //strange, unrecoverable error =(
+ Logger.getLogger(WebResponseImpl.class.getName())
+ .severe("Could not close input stream to in-memory byte array: " + e);
+ }
+ }
+ }
+ };
+ }
+
+ public int status() {
+ return response.getStatusCode();
+ }
+
+ @Override
+ public String toString() {
+ try {
+ return response.getResponseBody();
+ } catch (IOException e) {
+ // TODO
+ return "";
+ }
+ }
+}
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/ByteArrayTransport.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/ByteArrayTransport.java
new file mode 100644
index 00000000..f2d80806
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/ByteArrayTransport.java
@@ -0,0 +1,24 @@
+package com.google.sitebricks.client.transport;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+class ByteArrayTransport extends Xml {
+
+ @SuppressWarnings("unchecked")
+ public T in(InputStream in, Class type) throws IOException {
+ assert type == byte[].class;
+ return (T) IOUtils.toByteArray(in);
+ }
+
+ public void out(OutputStream out, Class type, T data) throws IOException {
+ assert data instanceof byte[];
+ out.write((byte[]) data);
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/JacksonJsonTransport.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/JacksonJsonTransport.java
new file mode 100644
index 00000000..30785a37
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/JacksonJsonTransport.java
@@ -0,0 +1,169 @@
+package com.google.sitebricks.client.transport;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Set;
+
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.JsonToken;
+import org.codehaus.jackson.map.DeserializationContext;
+import org.codehaus.jackson.map.JsonDeserializer;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.deser.CustomDeserializerFactory;
+import org.codehaus.jackson.map.deser.StdDeserializerProvider;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.common.primitives.Primitives;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.sitebricks.conversion.Converter;
+import com.google.sitebricks.conversion.ConverterRegistry;
+import com.google.sitebricks.conversion.StandardTypeConverter;
+import com.google.sitebricks.conversion.generics.Generics;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ * @author John Patterson (jdpatterson@gmail.com)
+ * @author JRodriguez
+ */
+@Singleton
+public class JacksonJsonTransport extends Json {
+
+ private final ObjectMapper objectMapper;
+ private Collection> exceptions = Sets.newHashSet();
+
+ @Inject
+ public JacksonJsonTransport(ConverterRegistry registry) {
+ this.objectMapper = new ObjectMapper();
+ CustomDeserializerFactory deserializerFactory = new CustomDeserializerFactory();
+
+ // leave these for Jackson to handle
+ exceptions.add(String.class);
+ exceptions.add(Object.class);
+ exceptions.addAll(Primitives.allWrapperTypes());
+
+ //
+ Multimap typeToConverterDirection = ArrayListMultimap.create();
+ addConverterDirections(registry, true, typeToConverterDirection);
+ addConverterDirections(registry, false, typeToConverterDirection);
+ createJacksonDeserializers(deserializerFactory, typeToConverterDirection);
+
+ objectMapper.setDeserializerProvider(new StdDeserializerProvider(deserializerFactory));
+ }
+
+ public ObjectMapper getObjectMapper() {
+ return objectMapper;
+ }
+
+ // keep track of which direction we want to use
+ private static class ConverterDirection
+ {
+ Converter, ?> converter;
+ boolean forward;
+ }
+
+ private void addConverterDirections(ConverterRegistry registry, boolean forward, Multimap typeToConverterDirections) {
+ Multimap> typeToConverters = forward ? registry.getConvertersByTarget() : registry.getConvertersBySource();
+ Set types = typeToConverters.keySet();
+ for (Type type : types) {
+ if (exceptions.contains(type)) continue;
+ Collection> converters = typeToConverters.get(type);
+ for (Converter, ?> converter : converters) {
+ ConverterDirection converterDirection = new ConverterDirection();
+ converterDirection.converter = converter;
+ converterDirection.forward = forward;
+ typeToConverterDirections.put(type, converterDirection);
+ }
+ }
+ }
+
+ private void createJacksonDeserializers(CustomDeserializerFactory deserializerFactory, Multimap typeToConverterDirections)
+ {
+ Set targetTypes = typeToConverterDirections.keySet();
+ for (Type targetType : targetTypes) {
+ Collection converterDirections = typeToConverterDirections.get(targetType);
+ Class> targetClass = Generics.erase(targetType);
+ ConvertersDeserializer jds = new ConvertersDeserializer(converterDirections);
+ typesafeAddMapping(targetClass, jds, deserializerFactory);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void typesafeAddMapping(Class> type, JsonDeserializer deserializer,
+ CustomDeserializerFactory factory) {
+ factory.addSpecificMapping((Class) type, deserializer);
+ }
+
+ public T in(InputStream in, Class type) throws IOException {
+ return objectMapper.readValue(in, type);
+ }
+
+ public void out(OutputStream out, Class type, T data) {
+ try {
+ objectMapper.writeValue(out, data);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public class ConvertersDeserializer extends JsonDeserializer {
+
+ private final Collection converterDirections;
+
+ public ConvertersDeserializer(Collection converterDirections) {
+ this.converterDirections = converterDirections;
+ }
+
+ public Object deserialize(JsonParser jp, DeserializationContext ctxt)
+ throws IOException, JsonProcessingException {
+
+ Object source = getSourceObject(jp, ctxt);
+
+ for (ConverterDirection converterDirection : converterDirections) {
+
+ Type sourceType = converterDirection.forward ?
+ StandardTypeConverter.sourceType(converterDirection.converter) :
+ StandardTypeConverter.targetType(converterDirection.converter);
+
+ // assume that Jackson only gives us non-generic types
+ Class> converterSourceClass = Generics.erase(sourceType);
+
+ if (converterSourceClass.isAssignableFrom(source.getClass())) {
+ return converterDirection.forward ?
+ StandardTypeConverter.typeSafeTo(converterDirection.converter, source) :
+ StandardTypeConverter.typeSafeFrom(converterDirection.converter, source);
+ }
+ }
+
+ throw new IllegalStateException("Cannot convert from " + source);
+ }
+
+ private Object getSourceObject(JsonParser jp, DeserializationContext ctxt) throws JsonParseException, IOException {
+ JsonToken t = jp.getCurrentToken();
+ if (t == JsonToken.VALUE_NUMBER_INT) {
+ return jp.getLongValue();
+ }
+ else if (t == JsonToken.VALUE_NUMBER_FLOAT) {
+ return jp.getDoubleValue();
+ }
+ else if (t == JsonToken.VALUE_TRUE) {
+ return Boolean.TRUE;
+ }
+ else if (t == JsonToken.VALUE_FALSE) {
+ return Boolean.FALSE;
+ }
+ else if (t == JsonToken.VALUE_STRING) {
+ return jp.getText();
+ }
+ else throw new IllegalStateException();
+ }
+ }
+}
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Json.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Json.java
new file mode 100644
index 00000000..e34ab8dc
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Json.java
@@ -0,0 +1,18 @@
+package com.google.sitebricks.client.transport;
+
+import com.google.inject.ImplementedBy;
+import com.google.sitebricks.client.Transport;
+
+/**
+ * A plain text (UTF-8) implementation of Transport where input types are assumed
+ * to be Strings.
+ *
+ * @author dhanji@google.com (Dhanji R. Prasanna)
+ */
+@ImplementedBy(JacksonJsonTransport.class)
+public abstract class Json implements Transport {
+
+ public String contentType() {
+ return "text/json";
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Raw.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Raw.java
new file mode 100644
index 00000000..6b08e84f
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Raw.java
@@ -0,0 +1,18 @@
+package com.google.sitebricks.client.transport;
+
+import com.google.inject.ImplementedBy;
+import com.google.sitebricks.client.Transport;
+
+/**
+ * A raw implementation of Transport where input types are assumed
+ * to be byte arrays.
+ *
+ * @author dhanji@google.com (Dhanji R. Prasanna)
+ */
+@ImplementedBy(ByteArrayTransport.class)
+public abstract class Raw implements Transport {
+
+ public String contentType() {
+ return "application/octet-stream";
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/SimpleTextTransport.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/SimpleTextTransport.java
new file mode 100644
index 00000000..bf7d973f
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/SimpleTextTransport.java
@@ -0,0 +1,24 @@
+package com.google.sitebricks.client.transport;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+class SimpleTextTransport extends Text {
+ public T in(InputStream in, Class type) throws IOException {
+ return type.cast(IOUtils.toString(in));
+ }
+
+ public void out(OutputStream out, Class type, T data) {
+ try {
+ IOUtils.write(data.toString(), out);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Text.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Text.java
new file mode 100644
index 00000000..b7313c24
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Text.java
@@ -0,0 +1,18 @@
+package com.google.sitebricks.client.transport;
+
+import com.google.inject.ImplementedBy;
+import com.google.sitebricks.client.Transport;
+
+/**
+ * A plain text (UTF-8) implementation of Transport where input types are assumed
+ * to be Strings.
+ *
+ * @author dhanji@google.com (Dhanji R. Prasanna)
+ */
+@ImplementedBy(SimpleTextTransport.class)
+public abstract class Text implements Transport {
+
+ public String contentType() {
+ return "text/plain";
+ }
+}
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/XStreamXmlTransport.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/XStreamXmlTransport.java
new file mode 100644
index 00000000..0c183684
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/XStreamXmlTransport.java
@@ -0,0 +1,28 @@
+package com.google.sitebricks.client.transport;
+
+import com.google.inject.Inject;
+import com.thoughtworks.xstream.XStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+class XStreamXmlTransport extends Xml {
+ private final XStream xStream;
+
+ @Inject
+ public XStreamXmlTransport(XStream xStream) {
+ this.xStream = xStream;
+ }
+
+ public T in(InputStream in, Class type) throws IOException {
+ return type.cast(xStream.fromXML(in));
+ }
+
+ public void out(OutputStream out, Class type, T data) {
+ xStream.toXML(data, out);
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Xml.java b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Xml.java
new file mode 100644
index 00000000..1cef36e0
--- /dev/null
+++ b/sitebricks-client/src/main/java/com/google/sitebricks/client/transport/Xml.java
@@ -0,0 +1,18 @@
+package com.google.sitebricks.client.transport;
+
+import com.google.inject.ImplementedBy;
+import com.google.sitebricks.client.Transport;
+
+/**
+ * A plain text (UTF-8) implementation of Transport where input types are assumed
+ * to be Strings.
+ *
+ * @author dhanji@google.com (Dhanji R. Prasanna)
+ */
+@ImplementedBy(XStreamXmlTransport.class)
+public abstract class Xml implements Transport {
+
+ public String contentType() {
+ return "text/xml";
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-client/src/test/java/com/google/sitebricks/client/WebClientEdslIntegrationTest.java b/sitebricks-client/src/test/java/com/google/sitebricks/client/WebClientEdslIntegrationTest.java
new file mode 100644
index 00000000..61f9d2c8
--- /dev/null
+++ b/sitebricks-client/src/test/java/com/google/sitebricks/client/WebClientEdslIntegrationTest.java
@@ -0,0 +1,43 @@
+package com.google.sitebricks.client;
+
+import com.google.inject.Guice;
+import com.google.sitebricks.client.transport.Text;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+public class WebClientEdslIntegrationTest {
+
+// @Test DISABLED
+ public final void edslForBinding() {
+ Web resource = Guice.createInjector().getInstance(Web.class);
+
+ WebClient webClient = resource.clientOf("http://google.com")
+ .transports(String.class)
+ .over(Text.class);
+
+ final WebResponse response = webClient.get();
+
+ final String responseAsString = response.toString();
+
+ assert responseAsString.contains("Google");
+ }
+
+// @Test DISABLED
+ public final void edslForBasicAuth() {
+ Web resource = Guice.createInjector().getInstance(Web.class);
+
+ WebClient webClient = resource.clientOf("http://twitter.com")
+ .auth(Web.Auth.BASIC, "dhanji@gmail.com", "mypass")
+ .transports(String.class)
+ .over(Text.class);
+
+ final WebResponse response = webClient.get();
+
+ final String responseAsString = response.toString();
+
+ System.out.println(responseAsString);
+
+ webClient.close();
+ }
+}
diff --git a/sitebricks-client/src/test/java/com/google/sitebricks/client/WebClientIntegrationTest.java b/sitebricks-client/src/test/java/com/google/sitebricks/client/WebClientIntegrationTest.java
new file mode 100644
index 00000000..1c53cad7
--- /dev/null
+++ b/sitebricks-client/src/test/java/com/google/sitebricks/client/WebClientIntegrationTest.java
@@ -0,0 +1,24 @@
+package com.google.sitebricks.client;
+
+import com.google.inject.Guice;
+import com.google.sitebricks.client.transport.Text;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ * @Expensive
+ */
+public class WebClientIntegrationTest {
+
+// @Test Disabled as you need to be online for this to work
+ public final void simpleJsonGetFromTwitter() {
+ Web web = Guice.createInjector().getInstance(Web.class);
+
+ WebClient webClient = web.clientOf("http://twitter.com/statuses/public_timeline.json")
+ .transports(String.class)
+ .over(Text.class);
+
+ final WebResponse response = webClient.get();
+
+ assert response.toString().contains("statuses");
+ }
+}
diff --git a/sitebricks-client/src/test/java/com/google/sitebricks/client/transport/RawTransportTest.java b/sitebricks-client/src/test/java/com/google/sitebricks/client/transport/RawTransportTest.java
new file mode 100644
index 00000000..70bc3df0
--- /dev/null
+++ b/sitebricks-client/src/test/java/com/google/sitebricks/client/transport/RawTransportTest.java
@@ -0,0 +1,39 @@
+package com.google.sitebricks.client.transport;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Unit test for the various transports supported out of the box.
+ */
+public class RawTransportTest {
+ private static final String TEXT_DATA = "text";
+
+ @DataProvider(name = TEXT_DATA)
+ public Object[][] textData() {
+ return new Object[][] {
+ { "Hello there 2793847!@(*(!*@ASDJFA M??X{." },
+ { "\\ \n \n \t \n \0 oaijsdfoijasdoifjao;sidjf19823749872w34*@(#$*&BMBMB" },
+ { "19827981273981723981729387192837912873912873" },
+ { " " },
+ { getClass().toString() },
+ { System.getProperties().toString() },
+ };
+ }
+
+ @Test(dataProvider = TEXT_DATA)
+ public final void textTransport(String data) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ new ByteArrayTransport().out(out, byte[].class, data.getBytes());
+
+ // Convert back from byte array to string.
+ String in = new String(new ByteArrayTransport()
+ .in(new ByteArrayInputStream(out.toByteArray()), byte[].class));
+
+ assert data.equals(in) : "Text transport was not balanced";
+ }
+}
diff --git a/sitebricks-client/src/test/java/com/google/sitebricks/client/transport/SimpleTextTransportTest.java b/sitebricks-client/src/test/java/com/google/sitebricks/client/transport/SimpleTextTransportTest.java
new file mode 100644
index 00000000..c98da866
--- /dev/null
+++ b/sitebricks-client/src/test/java/com/google/sitebricks/client/transport/SimpleTextTransportTest.java
@@ -0,0 +1,37 @@
+package com.google.sitebricks.client.transport;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Unit test for the various transports supported out of the box.
+ */
+public class SimpleTextTransportTest {
+ private static final String TEXT_DATA = "text";
+
+ @DataProvider(name = TEXT_DATA)
+ public Object[][] textData() {
+ return new Object[][] {
+ { "Hello there 2793847!@(*(!*@ASDJFA M??X{." },
+ { "\\ \n \n \t \n \0 oaijsdfoijasdoifjao;sidjf19823749872w34*@(#$*&BMBMB" },
+ { "19827981273981723981729387192837912873912873" },
+ { " " },
+ { getClass().toString() },
+ { System.getProperties().toString() },
+ };
+ }
+
+ @Test(dataProvider = TEXT_DATA)
+ public final void textTransport(String data) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ new SimpleTextTransport().out(out, String.class, data);
+
+ String in = new SimpleTextTransport().in(new ByteArrayInputStream(out.toByteArray()), String.class);
+
+ assert data.equals(in) : "Text transport was not balanced";
+ }
+}
diff --git a/sitebricks-client/src/test/java/com/google/sitebricks/client/transport/XmlTransportTest.java b/sitebricks-client/src/test/java/com/google/sitebricks/client/transport/XmlTransportTest.java
new file mode 100644
index 00000000..701daeee
--- /dev/null
+++ b/sitebricks-client/src/test/java/com/google/sitebricks/client/transport/XmlTransportTest.java
@@ -0,0 +1,78 @@
+package com.google.sitebricks.client.transport;
+
+import com.thoughtworks.xstream.XStream;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Date;
+
+/**
+ * Unit test for the xml transport supported out of the box.
+ */
+public class XmlTransportTest {
+ private static final String ROBOTS = "robots";
+
+ @DataProvider(name = ROBOTS)
+ Object[][] objects() {
+ return new Object[][] {
+ { new Robot("megatron", new Date(), 333, 12887L, null) },
+ { new Robot("meg aoskdoaks", new Date(192839), 3, 12887L,
+ new Robot("egatron", new Date(), 2193833, 12312887L, null)) },
+ { new Robot("iaisdja aijsd", new Date(1293891283), 333, 12887L, null) },
+ };
+ }
+
+ @Test(dataProvider = ROBOTS)
+ public final void xmlTransport(Robot robot) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ new XStreamXmlTransport(new XStream()).out(out, Robot.class, robot);
+ Robot in = new XStreamXmlTransport(new XStream()).in(new ByteArrayInputStream(out.toByteArray()), Robot.class);
+
+ assert robot.equals(in) : "Xml transport was not balanced";
+ }
+
+ public static class Robot {
+ public Robot(String name, Date time, int age, long looong, Robot pet) {
+ this.name = name;
+ this.time = time;
+ this.age = age;
+ this.looong = looong;
+ this.pet = pet;
+ }
+
+ private String name;
+ private Date time;
+ private int age;
+ private long looong = 123L;
+ private Robot pet;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Robot that = (Robot) o;
+
+ if (age != that.age) return false;
+ if (looong != that.looong) return false;
+ if (name != null ? !name.equals(that.name) : that.name != null) return false;
+ if (pet != null ? !pet.equals(that.pet) : that.pet != null) return false;
+ if (time != null ? !time.equals(that.time) : that.time != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name != null ? name.hashCode() : 0;
+ result = 31 * result + (time != null ? time.hashCode() : 0);
+ result = 31 * result + age;
+ result = 31 * result + (int) (looong ^ (looong >>> 32));
+ result = 31 * result + (pet != null ? pet.hashCode() : 0);
+ return result;
+ }
+ }
+}
diff --git a/sitebricks-client/src/test/resources/com/google/sitebricks/client/tweet.json b/sitebricks-client/src/test/resources/com/google/sitebricks/client/tweet.json
new file mode 100644
index 00000000..f7f2d379
--- /dev/null
+++ b/sitebricks-client/src/test/resources/com/google/sitebricks/client/tweet.json
@@ -0,0 +1,19 @@
+{ "user":
+ { "followers_count":303,
+ "description":"Hello! I'm new here :D",
+ "url":"",
+ "profile_image_url":"http:\/\/static.twitter.com\/images\/default_profile_normal.png",
+ "protected":false,"location":"",
+ "screen_name":"misterbr",
+ "name":"Mister B",
+ "id":21392498},
+
+ "text":"Watch Football Online - http:\/\/watch-football-live.co.cc\/about",
+ "truncated":false,
+ "favorited":false,
+ "in_reply_to_user_id":null,
+ "created_at":"Sun Apr 05 11:44:54 +0000 2009",
+ "source":"web",
+ "in_reply_to_status_id":null,
+ "id":1456580694
+}
\ No newline at end of file
diff --git a/sitebricks-converter/pom.xml b/sitebricks-converter/pom.xml
new file mode 100644
index 00000000..e77cbe57
--- /dev/null
+++ b/sitebricks-converter/pom.xml
@@ -0,0 +1,44 @@
+
+ 4.0.0
+
+ com.google.sitebricks
+ sitebricks-parent
+ 0.8.5
+
+ sitebricks-converter
+ Sitebricks :: Type Conversion
+
+
+
+ org.testng
+ testng
+ ${org.testng.version}
+ jdk15
+ test
+
+
+ org.mvel
+ mvel2
+
+
+ com.google.guava
+ guava
+
+
+ com.google.inject
+ guice
+
+
+ com.google.inject.extensions
+ guice-multibindings
+
+
+ org.codehaus.jackson
+ jackson-core-asl
+
+
+ org.codehaus.jackson
+ jackson-mapper-asl
+
+
+
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/Converter.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/Converter.java
new file mode 100644
index 00000000..f246b8c2
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/Converter.java
@@ -0,0 +1,18 @@
+package com.google.sitebricks.conversion;
+
+/**
+ * Convert an instance from type Source to type Target and back again.
+ *
+ * Returning null indicates that the conversion was not successful and another
+ * converter may be given the chance to handle it. Therefore, null is not a
+ * valid converted value and null will never be passed as a parameter.
+ *
+ * @author John Patterson (jdpatterson@gmail.com)
+ *
+ * @param Source Type
+ * @param Target Type
+ */
+public interface Converter {
+ T to(S source);
+ S from(T target);
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ConverterAdaptor.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ConverterAdaptor.java
new file mode 100644
index 00000000..3976e0a8
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ConverterAdaptor.java
@@ -0,0 +1,11 @@
+package com.google.sitebricks.conversion;
+
+/**
+ * @author John Patterson (jdpatterson@gmail.com)
+ */
+abstract class ConverterAdaptor implements Converter {
+ @Override
+ public S from(T target) {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ConverterRegistry.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ConverterRegistry.java
new file mode 100644
index 00000000..c23137db
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ConverterRegistry.java
@@ -0,0 +1,15 @@
+package com.google.sitebricks.conversion;
+
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+import com.google.common.collect.Multimap;
+import com.google.inject.ImplementedBy;
+
+@ImplementedBy(StandardTypeConverter.class)
+public interface ConverterRegistry {
+ void register(Converter, ?> converter);
+ Multimap> getConvertersByTarget();
+ Multimap> getConvertersBySource();
+ Collection> converter(Type source, Type target);
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ConverterUtils.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ConverterUtils.java
new file mode 100644
index 00000000..4e955ee6
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ConverterUtils.java
@@ -0,0 +1,34 @@
+package com.google.sitebricks.conversion;
+
+import com.google.inject.multibindings.Multibinder;
+
+public class ConverterUtils {
+ //
+ // I need to pass in the Multibinder because order in which bindings are made affects the
+ // outcome of the tests. So I would prefer to just hand back the Multibinder creating it from
+ // scratch but we can't right now. jvz. (yes, this class won't be around long)
+ //
+ public static Multibinder createConverterMultibinder(Multibinder converters) {
+ // register the default converters after user converters
+ converters.addBinding().to(ObjectToStringConverter.class);
+
+ // allow single request parameters to be converted to List
+ converters.addBinding().to(SingletonListConverter.class);
+
+ for( Converter,?> converter : StringToPrimitiveConverters.converters() )
+ {
+ converters.addBinding().toInstance(converter);
+ }
+
+ for( Converter,?> converter : NumberConverters.converters() )
+ {
+ converters.addBinding().toInstance(converter);
+ }
+
+ for( Class extends Converter, ?>> converterClass : DateConverters.converters())
+ {
+ converters.addBinding().to(converterClass);
+ }
+ return converters;
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/DateConverters.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/DateConverters.java
new file mode 100644
index 00000000..44190eb2
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/DateConverters.java
@@ -0,0 +1,170 @@
+package com.google.sitebricks.conversion;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+/**
+ * @author JRodriguez
+ * @author John Patterson (jdpatterson@gmail.com)
+ */
+public class DateConverters {
+ public static List>> converters() {
+ List>> converters = new ArrayList>>();
+ converters.add(LocalizedDateStringConverter.class);
+ converters.add(DateLongConverter.class);
+ converters.add(DateCalendarConverter.class);
+ converters.add(CalendarLongConverter.class);
+ converters.add(CalendarStringConverter.class);
+ return converters;
+ }
+
+ public static class DateLongConverter implements Converter {
+ @Override
+ public Date from(Long source) {
+ return new Date(source);
+ }
+
+ @Override
+ public Long to(Date target) {
+ return target.getTime();
+ }
+ }
+
+ public static class DateCalendarConverter implements Converter {
+
+ @Override
+ public Calendar to(Date source) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(source);
+ return calendar;
+ }
+
+ @Override
+ public Date from(Calendar target) {
+ return target.getTime();
+ }
+ }
+
+ public static class DateStringConverter implements Converter {
+
+ protected DateFormat format;
+
+ public DateStringConverter() {
+ this.format = DateFormat.getInstance();
+ }
+
+ public DateStringConverter(DateFormat format) {
+ this.format = format;
+ }
+
+ public DateStringConverter(String format) {
+ this.format = new SimpleDateFormat(format);
+ }
+
+ @Override
+ public Date from(String source) {
+ try {
+ return getFormat().parse(source);
+ }
+ catch (ParseException e) {
+ throw new IllegalArgumentException("Invalid date format", e);
+ }
+ }
+
+ @Override
+ public String to(Date target) {
+ return format.format(target);
+ }
+
+ protected DateFormat getFormat() {
+ return format;
+ }
+ }
+
+ public static class LocalizedDateStringConverter extends DateStringConverter {
+
+ private int dateStyle;
+ private int timeStyle;
+ private Provider provider;
+
+ public LocalizedDateStringConverter() {
+ this(DateFormat.LONG, DateFormat.LONG);
+ }
+ public LocalizedDateStringConverter(int dateStyle, int timeStyle) {
+ this.dateStyle = dateStyle;
+ this.timeStyle = timeStyle;
+ }
+
+ @Inject(optional=true)
+ public void setLocaleProvider(Provider provider) {
+ this.provider = provider;
+ }
+
+ @Override
+ protected DateFormat getFormat() {
+ if (provider != null) {
+ return DateFormat.getDateTimeInstance(dateStyle, timeStyle, provider.get());
+ }
+ else {
+ return super.getFormat();
+ }
+ }
+ }
+
+ public static class CalendarStringConverter implements Converter {
+
+ private final Provider provider;
+
+ @Inject
+ public CalendarStringConverter(Provider provider) {
+ this.provider = provider;
+ }
+
+ @Override
+ public String to(Calendar source) {
+ TypeConverter converter = provider.get();
+ Date date = converter.convert(source, Date.class);
+ return converter.convert(date, String.class);
+ }
+
+ @Override
+ public Calendar from(String target) {
+ TypeConverter converter = provider.get();
+ Date date = converter.convert(target, Date.class);
+ return converter.convert(date, Calendar.class);
+ }
+ }
+
+ public static class CalendarLongConverter implements Converter {
+
+ private final Provider provider;
+
+ @Inject
+ public CalendarLongConverter(Provider provider) {
+ this.provider = provider;
+ }
+
+ @Override
+ public Long to(Calendar source) {
+ TypeConverter converter = provider.get();
+ Date date = converter.convert(source, Date.class);
+ return converter.convert(date, Long.class);
+ }
+
+ @Override
+ public Calendar from(Long target) {
+ TypeConverter converter = provider.get();
+ Date date = converter.convert(target, Date.class);
+ return converter.convert(date, Calendar.class);
+ }
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/DummyTypeConverter.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/DummyTypeConverter.java
new file mode 100644
index 00000000..9daa288e
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/DummyTypeConverter.java
@@ -0,0 +1,19 @@
+package com.google.sitebricks.conversion;
+
+import java.lang.reflect.Type;
+
+/**
+ * Noop implementation that returns the source unaltered.
+ *
+ * @author John Patterson (jdpatterson@gmail.com)
+ *
+ */
+public class DummyTypeConverter implements TypeConverter
+{
+ @SuppressWarnings("unchecked")
+ @Override
+ public T convert(Object source, Type type)
+ {
+ return (T) source;
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/MvelConversionHandlers.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/MvelConversionHandlers.java
new file mode 100644
index 00000000..3f56bb35
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/MvelConversionHandlers.java
@@ -0,0 +1,69 @@
+package com.google.sitebricks.conversion;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+import org.mvel2.ConversionHandler;
+import org.mvel2.DataConversion;
+
+import com.google.common.primitives.Primitives;
+import com.google.inject.Inject;
+import com.google.sitebricks.conversion.generics.Generics;
+
+/**
+ * @author John Patterson (jdpatterson@gmail.com)
+ */
+public class MvelConversionHandlers {
+
+ private TypeConverter delegate;
+ private ConverterRegistry registry;
+
+ @Inject
+ public void prepare(ConverterRegistry registry, TypeConverter delegate) {
+ this.registry = registry;
+ this.delegate = delegate;
+
+ Collection> converters = registry.getConvertersBySource().values();
+ for (Converter, ?> converter : converters) {
+ ParameterizedType converterType = (ParameterizedType) Generics.getExactSuperType(
+ converter.getClass(), Converter.class);
+
+ Type[] converterParameters = converterType.getActualTypeArguments();
+ registerMvelHandler(converterParameters[0]);
+ registerMvelHandler(converterParameters[1]);
+ }
+ }
+
+ private void registerMvelHandler(Type targetType) {
+ Class> targetClass = Generics.erase(targetType);
+ SitebricksConversionHandler targetHandler = new SitebricksConversionHandler(targetType);
+ DataConversion.addConversionHandler(targetClass, targetHandler);
+ if (Primitives.isWrapperType(targetClass)) {
+ DataConversion.addConversionHandler(Primitives.unwrap(targetClass), targetHandler);
+ }
+ }
+
+ private class SitebricksConversionHandler implements ConversionHandler {
+ private final Type targetType;
+
+ public SitebricksConversionHandler(Type targetType) {
+ this.targetType = targetType;
+ }
+
+ @Override
+ public Object convertFrom(Object in) {
+ return delegate.convert(in, targetType);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean canConvertFrom(@SuppressWarnings("rawtypes") Class cls) {
+ if (cls == targetType)
+ return true;
+
+ // check that there is a converter registered for this source type
+ return registry.converter(Primitives.wrap(cls), targetType) != null;
+ }
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/MvelTypeConverter.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/MvelTypeConverter.java
new file mode 100644
index 00000000..149c76a1
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/MvelTypeConverter.java
@@ -0,0 +1,54 @@
+package com.google.sitebricks.conversion;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+
+import org.mvel2.DataConversion;
+
+import com.google.inject.Singleton;
+import com.google.sitebricks.conversion.TypeConverter;
+
+/**
+ * @author John Patterson (jdpatterson@gmail.com)
+ *
+ */
+@Singleton
+public class MvelTypeConverter implements TypeConverter {
+
+ @Override @SuppressWarnings("unchecked")
+ public T convert(Object source, Type type) {
+ return (T) DataConversion.convert(source, erase(type));
+ }
+
+ /**
+ * Returns the erasure of the given type.
+ * Taken from GenTyRef project
+ * TODO replace this once Sitebricks has internal generics utils
+ */
+ public static Class> erase(Type type) {
+ if (type instanceof Class>) {
+ return (Class>) type;
+ }
+ else if (type instanceof ParameterizedType) {
+ return (Class>) ((ParameterizedType) type).getRawType();
+ }
+ else if (type instanceof TypeVariable>) {
+ TypeVariable> tv = (TypeVariable>) type;
+ if (tv.getBounds().length == 0)
+ return Object.class;
+ else
+ return erase(tv.getBounds()[0]);
+ }
+ else if (type instanceof GenericArrayType) {
+ GenericArrayType aType = (GenericArrayType) type;
+ Class> componentType = erase(aType.getGenericComponentType());
+ return Array.newInstance(componentType, 0).getClass();
+ }
+ else {
+ throw new RuntimeException("not supported: " + type.getClass());
+ }
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/NumberConverters.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/NumberConverters.java
new file mode 100644
index 00000000..b598eca6
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/NumberConverters.java
@@ -0,0 +1,53 @@
+package com.google.sitebricks.conversion;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author John Patterson (jdpatterson@gmail.com)
+ */
+public class NumberConverters {
+ public static List> converters() {
+ List> converters = new ArrayList>();
+
+ converters.add(new ConverterAdaptor() {
+ public Integer to(Number source) {
+ return Integer.valueOf(source.intValue());
+ }
+ });
+ converters.add(new ConverterAdaptor() {
+ public Long to(Number source) {
+ return Long.valueOf(source.longValue());
+ }
+ });
+ converters.add(new ConverterAdaptor() {
+ public Float to(Number source) {
+ return Float.valueOf(source.floatValue());
+ }
+ });
+ converters.add(new ConverterAdaptor() {
+ public Double to(Number source) {
+ return Double.valueOf(source.doubleValue());
+ }
+ });
+ converters.add(new ConverterAdaptor() {
+ public Short to(Number source) {
+ return Short.valueOf(source.shortValue());
+ }
+ });
+ converters.add(new ConverterAdaptor() {
+ public BigInteger to(Number source) {
+ return BigInteger.valueOf(source.longValue());
+ }
+ });
+ converters.add(new ConverterAdaptor() {
+ public BigDecimal to(Number source) {
+ return BigDecimal.valueOf(source.doubleValue());
+ }
+ });
+
+ return converters;
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ObjectToStringConverter.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ObjectToStringConverter.java
new file mode 100644
index 00000000..f44aa4f7
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/ObjectToStringConverter.java
@@ -0,0 +1,12 @@
+package com.google.sitebricks.conversion;
+
+import com.google.inject.Singleton;
+
+@Singleton
+public class ObjectToStringConverter extends ConverterAdaptor {
+
+ @Override
+ public String to(Object source) {
+ return source.toString();
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/SingletonListConverter.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/SingletonListConverter.java
new file mode 100644
index 00000000..58c4a566
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/SingletonListConverter.java
@@ -0,0 +1,17 @@
+package com.google.sitebricks.conversion;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class SingletonListConverter implements Converter> {
+
+ @Override
+ public List> to(Object source) {
+ return Arrays.asList(source);
+ }
+
+ @Override
+ public Object from(List> target) {
+ return target.get(0);
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/StandardTypeConverter.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/StandardTypeConverter.java
new file mode 100644
index 00000000..93818d1c
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/StandardTypeConverter.java
@@ -0,0 +1,219 @@
+package com.google.sitebricks.conversion;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.primitives.Primitives;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.sitebricks.conversion.generics.Generics;
+
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Set;
+
+import static com.google.sitebricks.conversion.generics.Generics.erase;
+import static com.google.sitebricks.conversion.generics.Generics.getExactSuperType;
+import static com.google.sitebricks.conversion.generics.Generics.getTypeParameter;
+
+/**
+ * @author John Patterson (jdpatterson@gmail.com)
+ */
+@Singleton
+public class StandardTypeConverter implements TypeConverter, ConverterRegistry {
+ Multimap> convertersBySource = ArrayListMultimap.create();
+ Multimap> convertersByTarget = ArrayListMultimap.create();
+
+ Multimap> convertersBySourceAndTarget = ArrayListMultimap.create();
+ private static final TypeVariable extends Class>> sourceTypeParameter = Converter.class.getTypeParameters()[0];
+ private static final TypeVariable extends Class>> targetTypeParameter = Converter.class.getTypeParameters()[1];
+
+ @Inject
+ public StandardTypeConverter(@SuppressWarnings("rawtypes") Set converters) {
+ for (Converter, ?> converter : converters) {
+ register(converter);
+ }
+ }
+
+ @Override
+ public void register(Converter, ?> converter) {
+ // get the source and target types
+ Type sourceType = sourceType(converter);
+ Type targetType = targetType(converter);
+ convertersBySource.put(sourceType, converter);
+ convertersByTarget.put(targetType, converter);
+ convertersBySourceAndTarget.put(new SourceAndTarget(sourceType, targetType), converter);
+ }
+
+ public static Type targetType(Converter, ?> converter) {
+ return getTypeParameter(converter.getClass(), targetTypeParameter);
+ }
+
+ public static Type sourceType(Converter, ?> converter) {
+ return getTypeParameter(converter.getClass(), sourceTypeParameter);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T convert(final Object source, Type type) {
+
+ // special case for handling a null source values
+ if (source == null) {
+ return (T) nullValue(type);
+ }
+
+ // check we already have the exact type
+ if (source.getClass() == type) {
+ return (T) source;
+ }
+
+ // check if we already have a sub type
+ if (Generics.isSuperType(type, source.getClass()))
+ {
+ return (T) source;
+ }
+
+ // special case for handling empty string
+ if ("".equals(source) && type != String.class && isEmptyStringNull()) {
+ return null;
+ }
+
+ Type sourceType = source.getClass();
+ Class> sourceClass = Generics.erase(sourceType);
+
+ // conversion of all array types to collections
+ if (sourceClass.isArray() && Generics.isSuperType(Collection.class, type)) {
+ return (T) Arrays.asList(source);
+ }
+
+ // conversion of all collections to arrays
+ Class> targetClass = Generics.erase(type);
+ if (Collection.class.isAssignableFrom(sourceClass) && targetClass.isArray()) {
+ // TODO: convert collections to arrays
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ // use primitive wrapper types
+ if (type instanceof Class> && ((Class>) type).isPrimitive()) {
+ type = Primitives.wrap((Class>) type);
+ }
+
+
+ // look for converters for exact types or super types
+ Object result = null;
+ do {
+
+ // first try to find a converter in the forward direction
+ SourceAndTarget key = new SourceAndTarget(sourceType, type);
+ Collection> forwards = convertersBySourceAndTarget.get(key);
+
+ // stop at the first converter that returns non-null
+ for (Converter, ?> forward : forwards)
+ if ((result = typeSafeTo(forward, source)) != null) break;
+
+ if (result == null) {
+ // try the reverse direction (target to source)
+ Collection> reverses = convertersBySourceAndTarget.get(key.reverse());
+
+ // stop at the first converter that returns non-null
+ for (Converter, ?> reverse : reverses)
+ if ((result = typeSafeFrom(reverse, source)) != null) break;
+ }
+
+ // we have no more super classes to try
+ if (sourceType == Object.class) break;
+
+ // try every super type of the source
+ Class> superClass = erase(sourceType).getSuperclass();
+ sourceType = getExactSuperType(sourceType, superClass);
+ } while (result == null);
+
+ if (result == null)
+ throw new IllegalStateException("Cannot convert " + source.getClass() + " to " + type);
+
+ return (T) result;
+ }
+
+ @Override
+ public Collection> converter(Type source, Type target) {
+ SourceAndTarget key = new SourceAndTarget(source, target);
+ return convertersBySourceAndTarget.get(key);
+ }
+
+ protected boolean isEmptyStringNull() {
+ return true;
+ }
+
+ protected Object nullValue(Type type) {
+ if (type == String.class) {
+ return "";
+ }
+ else return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T typeSafeTo(Converter, ?> converter, S source) {
+ return ((Converter) converter).to(source);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static S typeSafeFrom(Converter, ?> converter, T source) {
+ return ((Converter) converter).from(source);
+ }
+
+ @Override
+ public Multimap> getConvertersBySource() {
+ return convertersBySource;
+ }
+
+ @Override
+ public Multimap> getConvertersByTarget() {
+ return convertersByTarget;
+ }
+
+ private static final class SourceAndTarget {
+ private Type source;
+ private Type target;
+
+ public SourceAndTarget(Type source, Type target) {
+ this.source = source;
+ this.target = target;
+ }
+
+ public SourceAndTarget reverse() {
+ return new SourceAndTarget(target, source);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((source == null) ? 0 : source.hashCode());
+ result = prime * result + ((target == null) ? 0 : target.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SourceAndTarget other = (SourceAndTarget) obj;
+ if (source == null) {
+ if (other.source != null)
+ return false;
+ } else if (!source.equals(other.source))
+ return false;
+ if (target == null) {
+ if (other.target != null)
+ return false;
+ } else if (!target.equals(other.target))
+ return false;
+ return true;
+ }
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/StringToPrimitiveConverters.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/StringToPrimitiveConverters.java
new file mode 100644
index 00000000..a14992ea
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/StringToPrimitiveConverters.java
@@ -0,0 +1,50 @@
+package com.google.sitebricks.conversion;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author John Patterson (jdpatterson@gmail.com)
+ */
+public class StringToPrimitiveConverters {
+ public static List> converters() {
+ List> converters = new ArrayList>();
+ converters.add(new ConverterAdaptor() {
+ public Integer to(String source) {
+ return Integer.valueOf(source);
+ }
+ });
+ converters.add(new ConverterAdaptor() {
+ public Long to(String source) {
+ return Long.valueOf(source);
+ }
+ });
+ converters.add(new ConverterAdaptor() {
+ public Float to(String source) {
+ return Float.valueOf(source);
+ }
+ });
+ converters.add(new ConverterAdaptor() {
+ public Double to(String source) {
+ return Double.valueOf(source);
+ }
+ });
+ converters.add(new ConverterAdaptor() {
+ public Byte to(String source) {
+ return Byte.valueOf(source);
+ }
+ });
+ converters.add(new ConverterAdaptor() {
+ public Boolean to(String source) {
+ return Boolean.valueOf(source);
+ }
+ });
+ converters.add(new ConverterAdaptor() {
+ public Character to(String source) {
+ return source.charAt(0);
+ }
+ });
+
+ return converters;
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/TypeConverter.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/TypeConverter.java
new file mode 100644
index 00000000..62b4e7a9
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/TypeConverter.java
@@ -0,0 +1,23 @@
+package com.google.sitebricks.conversion;
+
+import com.google.inject.ImplementedBy;
+
+import java.lang.reflect.Type;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ * @author John Patterson (jdpatterson@gmail.com)
+ * @author JRodriguez
+ */
+@ImplementedBy(StandardTypeConverter.class)
+public interface TypeConverter {
+
+ /**
+ * Convert an instance to the given type.
+ *
+ * @param source Original instance
+ * @param type The type to convert to.
+ * @return A converted instance of type {@code Type}}
+ */
+ T convert(Object source, Type type);
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/CaptureType.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/CaptureType.java
new file mode 100644
index 00000000..c2f3e019
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/CaptureType.java
@@ -0,0 +1,35 @@
+/*
+ * Copied from Gentyref project http://code.google.com/p/gentyref/
+ * Reformatted and moved to fit package structure
+ */
+package com.google.sitebricks.conversion.generics;
+
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
+
+/**
+ * CaptureType represents a wildcard that has gone through capture conversion.
+ * It is a custom subinterface of Type, not part of the java builtin Type
+ * hierarchy.
+ *
+ * @author Wouter Coekaerts
+ */
+public interface CaptureType extends Type
+{
+ /**
+ * Returns an array of Type objects representing the upper bound(s)
+ * of this capture. This includes both the upper bound of a
+ * ? extends wildcard, and the bounds declared with the type
+ * variable. References to other (or the same) type variables in bounds
+ * coming from the type variable are replaced by their matching capture.
+ */
+ Type[] getUpperBounds();
+
+ /**
+ * Returns an array of Type objects representing the lower bound(s)
+ * of this type variable. This is the bound of a ? super wildcard.
+ * This normally contains only one or no types; it is an array for
+ * consistency with {@link WildcardType#getLowerBounds()}.
+ */
+ Type[] getLowerBounds();
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/CaptureTypeImpl.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/CaptureTypeImpl.java
new file mode 100644
index 00000000..bcc9aac0
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/CaptureTypeImpl.java
@@ -0,0 +1,71 @@
+/*
+ * Copied from Gentyref project http://code.google.com/p/gentyref/
+ * Reformatted and moved to fit package structure
+ */
+package com.google.sitebricks.conversion.generics;
+
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+class CaptureTypeImpl implements CaptureType
+{
+ private final WildcardType wildcard;
+ private final TypeVariable> variable;
+ private final Type[] lowerBounds;
+ private Type[] upperBounds;
+
+ /**
+ * Creates an uninitialized CaptureTypeImpl. Before using this type,
+ * {@link #init(VarMap)} must be called.
+ *
+ * @param wildcard
+ * The wildcard this is a capture of
+ * @param variable
+ * The type variable where the wildcard is a parameter for.
+ */
+ public CaptureTypeImpl(WildcardType wildcard, TypeVariable> variable)
+ {
+ this.wildcard = wildcard;
+ this.variable = variable;
+ this.lowerBounds = wildcard.getLowerBounds();
+ }
+
+ /**
+ * Initialize this CaptureTypeImpl. This is needed for type variable bounds
+ * referring to each other: we need the capture of the argument.
+ */
+ void init(VarMap varMap)
+ {
+ ArrayList upperBoundsList = new ArrayList();
+ upperBoundsList.addAll(Arrays.asList(varMap.map(variable.getBounds())));
+ upperBoundsList.addAll(Arrays.asList(wildcard.getUpperBounds()));
+ upperBounds = new Type[upperBoundsList.size()];
+ upperBoundsList.toArray(upperBounds);
+ }
+
+ /*
+ * @see com.googlecode.gentyref.CaptureType#getLowerBounds()
+ */
+ public Type[] getLowerBounds()
+ {
+ return lowerBounds.clone();
+ }
+
+ /*
+ * @see com.googlecode.gentyref.CaptureType#getUpperBounds()
+ */
+ public Type[] getUpperBounds()
+ {
+ assert upperBounds != null;
+ return upperBounds.clone();
+ }
+
+ @Override
+ public String toString()
+ {
+ return "capture of " + wildcard;
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/GenericArrayTypeImpl.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/GenericArrayTypeImpl.java
new file mode 100644
index 00000000..2d2024fc
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/GenericArrayTypeImpl.java
@@ -0,0 +1,64 @@
+/*
+ * Copied from Gentyref project http://code.google.com/p/gentyref/
+ * Code was reformatted and moved to fit package structure
+ */
+package com.google.sitebricks.conversion.generics;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Type;
+
+class GenericArrayTypeImpl implements GenericArrayType
+{
+ private Type componentType;
+
+ static Class> createArrayType(Class> componentType)
+ {
+ // there's no (clean) other way to create a array class, then create an
+ // instance of it
+ return Array.newInstance(componentType, 0).getClass();
+ }
+
+ static Type createArrayType(Type componentType)
+ {
+ if (componentType instanceof Class>)
+ {
+ return createArrayType((Class>) componentType);
+ }
+ else
+ {
+ return new GenericArrayTypeImpl(componentType);
+ }
+ }
+
+ private GenericArrayTypeImpl(Type componentType)
+ {
+ super();
+ this.componentType = componentType;
+ }
+
+ public Type getGenericComponentType()
+ {
+ return componentType;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (!(obj instanceof GenericArrayType))
+ return false;
+ return componentType.equals(((GenericArrayType) obj).getGenericComponentType());
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return componentType.hashCode() * 7;
+ }
+
+ @Override
+ public String toString()
+ {
+ return componentType + "[]";
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/Generics.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/Generics.java
new file mode 100644
index 00000000..06c7233a
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/Generics.java
@@ -0,0 +1,547 @@
+/*
+ * Copied from Gentyref project http://code.google.com/p/gentyref/
+ * Code was reformatted and moved to fit package structure
+ */
+package com.google.sitebricks.conversion.generics;
+
+
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class Generics {
+ private static final Type UNBOUND_WILDCARD = new WildcardTypeImpl(new Type[] { Object.class },
+ new Type[] {});
+
+ /**
+ * Returns the erasure of the given type.
+ */
+ public static Class> erase(Type type)
+ {
+ if (type instanceof Class>)
+ {
+ return (Class>) type;
+ }
+ else if (type instanceof ParameterizedType)
+ {
+ return (Class>) ((ParameterizedType) type).getRawType();
+ }
+ else if (type instanceof TypeVariable>)
+ {
+ TypeVariable> tv = (TypeVariable>) type;
+ if (tv.getBounds().length == 0)
+ return Object.class;
+ else
+ return erase(tv.getBounds()[0]);
+ }
+ else if (type instanceof GenericArrayType)
+ {
+ GenericArrayType aType = (GenericArrayType) type;
+ return GenericArrayTypeImpl.createArrayType(erase(aType.getGenericComponentType()));
+ }
+ else
+ {
+ // TODO at least support CaptureType here
+ throw new RuntimeException("not supported: " + type.getClass());
+ }
+ }
+
+ /**
+ * Maps type parameters in a type to their values.
+ *
+ * @param toMapType
+ * Type possibly containing type arguments
+ * @param typeAndParams
+ * must be either ParameterizedType, or (in case there are no
+ * type arguments, or it's a raw type) Class
+ * @return toMapType, but with type parameters from typeAndParams replaced.
+ */
+ private static Type mapTypeParameters(Type toMapType, Type typeAndParams)
+ {
+ if (isMissingTypeParameters(typeAndParams))
+ {
+ return erase(toMapType);
+ }
+ else
+ {
+ VarMap varMap = new VarMap();
+ Type handlingTypeAndParams = typeAndParams;
+ while (handlingTypeAndParams instanceof ParameterizedType)
+ {
+ ParameterizedType pType = (ParameterizedType) handlingTypeAndParams;
+ Class> clazz = (Class>) pType.getRawType(); // getRawType
+ // should always
+ // be Class
+ varMap.addAll(clazz.getTypeParameters(), pType.getActualTypeArguments());
+ handlingTypeAndParams = pType.getOwnerType();
+ }
+ return varMap.map(toMapType);
+ }
+ }
+
+ /**
+ * Checks if the given type is a class that is supposed to have type
+ * parameters, but doesn't. In other words, if it's a really raw type.
+ */
+ private static boolean isMissingTypeParameters(Type type)
+ {
+ if (type instanceof Class>)
+ {
+ for (Class> clazz = (Class>) type; clazz != null; clazz = clazz.getEnclosingClass())
+ {
+ if (clazz.getTypeParameters().length != 0)
+ return true;
+ }
+ return false;
+ }
+ else if (type instanceof ParameterizedType)
+ {
+ return false;
+ }
+ else
+ {
+ throw new AssertionError("Unexpected type " + type.getClass());
+ }
+ }
+
+ /**
+ * Returns a type representing the class, with all type parameters the
+ * unbound wildcard ("?"). For example,
+ * addWildcardParameters(Map.class) returns a type representing
+ * Map<?,?> .
+ *
+ * @return
+ * If clazz is a class or interface without type parameters,
+ * clazz itself is returned.
+ * If clazz is a class or interface with type parameters, an
+ * instance of ParameterizedType is returned.
+ * if clazz is an array type, an array type is returned with
+ * unbound wildcard parameters added in the the component type.
+ *
+ */
+ public static Type addWildcardParameters(Class> clazz)
+ {
+ if (clazz.isArray())
+ {
+ return GenericArrayTypeImpl.createArrayType(addWildcardParameters(clazz
+ .getComponentType()));
+ }
+ else if (isMissingTypeParameters(clazz))
+ {
+ TypeVariable>[] vars = clazz.getTypeParameters();
+ Type[] arguments = new Type[vars.length];
+ Arrays.fill(arguments, UNBOUND_WILDCARD);
+ Type owner = clazz.getDeclaringClass() == null ? null : addWildcardParameters(clazz
+ .getDeclaringClass());
+ return new ParameterizedTypeImpl(clazz, arguments, owner);
+ }
+ else
+ {
+ return clazz;
+ }
+ }
+
+ /**
+ * With type a supertype of searchClass, returns the exact supertype of the
+ * given class, including type parameters. For example, with
+ * class StringList implements List<String> ,
+ * getExactSuperType(StringList.class, Collection.class) returns a
+ * {@link ParameterizedType} representing Collection<String> .
+ *
+ * Returns null if searchClass is not a superclass of type.
+ * Returns an instance of {@link Class} if type if it is a raw
+ * type, or has no type parameters
+ * Returns an instance of {@link ParameterizedType} if the type does
+ * have parameters
+ * Returns an instance of {@link GenericArrayType} if
+ * searchClass is an array type, and the actual type has type
+ * parameters
+ *
+ */
+ public static Type getExactSuperType(Type type, Class> searchClass)
+ {
+ if (type instanceof ParameterizedType || type instanceof Class>
+ || type instanceof GenericArrayType)
+ {
+ Class> clazz = erase(type);
+
+ if (searchClass == clazz)
+ {
+ return type;
+ }
+
+ if (!searchClass.isAssignableFrom(clazz))
+ return null;
+ }
+
+ for (Type superType : getExactDirectSuperTypes(type))
+ {
+ Type result = getExactSuperType(superType, searchClass);
+ if (result != null)
+ return result;
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the type parameter for a given type that is the value for a given
+ * type variable. For example, with
+ * class StringList implements List<String> ,
+ * getTypeParameter(StringList.class, Collection.class.getTypeParameters()[0])
+ * returns String .
+ *
+ * @param type
+ * The type to inspect.
+ * @param variable
+ * The type variable to find the value for.
+ * @return The type parameter for the given variable. Or null if type is not
+ * a subtype of the type that declares the variable, or if the
+ * variable isn't known (because of raw types).
+ */
+ public static Type getTypeParameter(Type type, TypeVariable extends Class>> variable)
+ {
+ Class> clazz = variable.getGenericDeclaration();
+ Type superType = getExactSuperType(type, clazz);
+ if (superType instanceof ParameterizedType)
+ {
+ int index = Arrays.asList(clazz.getTypeParameters()).indexOf(variable);
+ return ((ParameterizedType) superType).getActualTypeArguments()[index];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Checks if the capture of subType is a subtype of superType
+ */
+ public static boolean isSuperType(Type superType, Type subType)
+ {
+ if (superType instanceof ParameterizedType || superType instanceof Class>
+ || superType instanceof GenericArrayType)
+ {
+ Class> superClass = erase(superType);
+ Type mappedSubType = getExactSuperType(capture(subType), superClass);
+ if (mappedSubType == null)
+ {
+ return false;
+ }
+ else if (superType instanceof Class>)
+ {
+ return true;
+ }
+ else if (mappedSubType instanceof Class>)
+ {
+ // TODO treat supertype by being raw type differently
+ // ("supertype, but with warnings")
+ return true; // class has no parameters, or it's a raw type
+ }
+ else if (mappedSubType instanceof GenericArrayType)
+ {
+ Type superComponentType = getArrayComponentType(superType);
+ assert superComponentType != null;
+ Type mappedSubComponentType = getArrayComponentType(mappedSubType);
+ assert mappedSubComponentType != null;
+ return isSuperType(superComponentType, mappedSubComponentType);
+ }
+ else
+ {
+ assert mappedSubType instanceof ParameterizedType;
+ ParameterizedType pMappedSubType = (ParameterizedType) mappedSubType;
+ assert pMappedSubType.getRawType() == superClass;
+ ParameterizedType pSuperType = (ParameterizedType) superType;
+
+ Type[] superTypeArgs = pSuperType.getActualTypeArguments();
+ Type[] subTypeArgs = pMappedSubType.getActualTypeArguments();
+ assert superTypeArgs.length == subTypeArgs.length;
+ for (int i = 0; i < superTypeArgs.length; i++)
+ {
+ if (!contains(superTypeArgs[i], subTypeArgs[i]))
+ {
+ return false;
+ }
+ }
+ // params of the class itself match, so if the owner types are
+ // supertypes too, it's a supertype.
+ return pSuperType.getOwnerType() == null
+ || isSuperType(pSuperType.getOwnerType(), pMappedSubType.getOwnerType());
+ }
+ }
+ else if (superType instanceof CaptureType)
+ {
+ if (superType.equals(subType))
+ return true;
+ for (Type lowerBound : ((CaptureType) superType).getLowerBounds())
+ {
+ if (isSuperType(lowerBound, subType))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ else if (superType instanceof GenericArrayType)
+ {
+ return isArraySupertype(superType, subType);
+ }
+ else
+ {
+ throw new RuntimeException("not implemented: " + superType.getClass());
+ }
+ }
+
+ private static boolean isArraySupertype(Type arraySuperType, Type subType)
+ {
+ Type superTypeComponent = getArrayComponentType(arraySuperType);
+ assert superTypeComponent != null;
+ Type subTypeComponent = getArrayComponentType(subType);
+ if (subTypeComponent == null)
+ { // subType is not an array type
+ return false;
+ }
+ else
+ {
+ return isSuperType(superTypeComponent, subTypeComponent);
+ }
+ }
+
+ /**
+ * If type is an array type, returns the type of the component of the array.
+ * Otherwise, returns null.
+ */
+ public static Type getArrayComponentType(Type type)
+ {
+ if (type instanceof Class>)
+ {
+ Class> clazz = (Class>) type;
+ return clazz.getComponentType();
+ }
+ else if (type instanceof GenericArrayType)
+ {
+ GenericArrayType aType = (GenericArrayType) type;
+ return aType.getGenericComponentType();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ private static boolean contains(Type containingType, Type containedType)
+ {
+ if (containingType instanceof WildcardType)
+ {
+ WildcardType wContainingType = (WildcardType) containingType;
+ for (Type upperBound : wContainingType.getUpperBounds())
+ {
+ if (!isSuperType(upperBound, containedType))
+ {
+ return false;
+ }
+ }
+ for (Type lowerBound : wContainingType.getLowerBounds())
+ {
+ if (!isSuperType(containedType, lowerBound))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ else
+ {
+ return containingType.equals(containedType);
+ }
+ }
+
+ /**
+ * Returns the direct supertypes of the given type. Resolves type
+ * parameters.
+ */
+ public static Type[] getExactDirectSuperTypes(Type type)
+ {
+ if (type instanceof ParameterizedType || type instanceof Class>)
+ {
+ Class> clazz;
+ if (type instanceof ParameterizedType)
+ {
+ clazz = (Class>) ((ParameterizedType) type).getRawType();
+ }
+ else
+ {
+ // TODO primitive types?
+ clazz = (Class>) type;
+ if (clazz.isArray())
+ return getArrayExactDirectSuperTypes(clazz);
+ }
+
+ Type[] superInterfaces = clazz.getGenericInterfaces();
+ Type superClass = clazz.getGenericSuperclass();
+ Type[] result;
+ int resultIndex;
+ if (superClass == null)
+ {
+ result = new Type[superInterfaces.length];
+ resultIndex = 0;
+ }
+ else
+ {
+ result = new Type[superInterfaces.length + 1];
+ resultIndex = 1;
+ result[0] = mapTypeParameters(superClass, type);
+ }
+ for (Type superInterface : superInterfaces)
+ {
+ result[resultIndex++] = mapTypeParameters(superInterface, type);
+ }
+
+ return result;
+ }
+ else if (type instanceof TypeVariable>)
+ {
+ TypeVariable> tv = (TypeVariable>) type;
+ return tv.getBounds();
+ }
+ else if (type instanceof WildcardType)
+ {
+ // This should be a rare case: normally this wildcard is already
+ // captured.
+ // But it does happen if the upper bound of a type variable contains
+ // a wildcard
+ // TODO shouldn't upper bound of type variable have been captured
+ // too? (making this case impossible?)
+ return ((WildcardType) type).getUpperBounds();
+ }
+ else if (type instanceof CaptureType)
+ {
+ return ((CaptureType) type).getUpperBounds();
+ }
+ else if (type instanceof GenericArrayType)
+ {
+ return getArrayExactDirectSuperTypes(type);
+ }
+ else
+ {
+ throw new RuntimeException("not implemented type: " + type);
+ }
+ }
+
+ private static Type[] getArrayExactDirectSuperTypes(Type arrayType)
+ {
+ // see
+ // http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.10.3
+ Type typeComponent = getArrayComponentType(arrayType);
+
+ Type[] result;
+ int resultIndex;
+ if (typeComponent instanceof Class> && ((Class>) typeComponent).isPrimitive())
+ {
+ resultIndex = 0;
+ result = new Type[3];
+ }
+ else
+ {
+ Type[] componentSupertypes = getExactDirectSuperTypes(typeComponent);
+ result = new Type[componentSupertypes.length + 3];
+ for (resultIndex = 0; resultIndex < componentSupertypes.length; resultIndex++)
+ {
+ result[resultIndex] = GenericArrayTypeImpl
+ .createArrayType(componentSupertypes[resultIndex]);
+ }
+ }
+ result[resultIndex++] = Object.class;
+ result[resultIndex++] = Cloneable.class;
+ result[resultIndex++] = Serializable.class;
+ return result;
+ }
+
+ /**
+ * Returns the exact return type of the given method in the given type. This
+ * may be different from m.getGenericReturnType() when the method
+ * was declared in a superclass, of type is a raw type.
+ */
+ public static Type getExactReturnType(Method m, Type type)
+ {
+ Type returnType = m.getGenericReturnType();
+ Type exactDeclaringType = getExactSuperType(capture(type), m.getDeclaringClass());
+ return mapTypeParameters(returnType, exactDeclaringType);
+ }
+
+ /**
+ * Returns the exact type of the given field in the given type. This may be
+ * different from f.getGenericType() when the field was declared in
+ * a superclass, of type is a raw type.
+ */
+ public static Type getExactFieldType(Field f, Type type)
+ {
+ Type returnType = f.getGenericType();
+ Type exactDeclaringType = getExactSuperType(capture(type), f.getDeclaringClass());
+ return mapTypeParameters(returnType, exactDeclaringType);
+ }
+
+ /**
+ * Applies capture conversion to the given type.
+ */
+ public static Type capture(Type type)
+ {
+ VarMap varMap = new VarMap();
+ List toInit = new ArrayList();
+ if (type instanceof ParameterizedType)
+ {
+ ParameterizedType pType = (ParameterizedType) type;
+ Class> clazz = (Class>) pType.getRawType();
+ Type[] arguments = pType.getActualTypeArguments();
+ TypeVariable>[] vars = clazz.getTypeParameters();
+ Type[] capturedArguments = new Type[arguments.length];
+ assert arguments.length == vars.length;
+ for (int i = 0; i < arguments.length; i++)
+ {
+ Type argument = arguments[i];
+ if (argument instanceof WildcardType)
+ {
+ CaptureTypeImpl captured = new CaptureTypeImpl((WildcardType) argument, vars[i]);
+ argument = captured;
+ toInit.add(captured);
+ }
+ capturedArguments[i] = argument;
+ varMap.add(vars[i], argument);
+ }
+ for (CaptureTypeImpl captured : toInit)
+ {
+ captured.init(varMap);
+ }
+ Type ownerType = (pType.getOwnerType() == null) ? null : capture(pType.getOwnerType());
+ return new ParameterizedTypeImpl(clazz, capturedArguments, ownerType);
+ }
+ else
+ {
+ return type;
+ }
+ }
+
+ /**
+ * Returns the display name of a Type.
+ */
+ public static String getTypeName(Type type)
+ {
+ if (type instanceof Class>)
+ {
+ Class> clazz = (Class>) type;
+ return clazz.isArray() ? (getTypeName(clazz.getComponentType()) + "[]") : clazz.getName();
+ }
+ else
+ {
+ return type.toString();
+ }
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/ParameterizedTypeImpl.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/ParameterizedTypeImpl.java
new file mode 100644
index 00000000..5a67f0fd
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/ParameterizedTypeImpl.java
@@ -0,0 +1,95 @@
+/*
+ * Copied from Gentyref project http://code.google.com/p/gentyref/
+ * Reformatted and moved to fit package structure
+ */
+package com.google.sitebricks.conversion.generics;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+
+public class ParameterizedTypeImpl implements ParameterizedType
+{
+ private final Class> rawType;
+ private final Type[] actualTypeArguments;
+ private final Type ownerType;
+
+ public ParameterizedTypeImpl(Class> rawType, Type[] actualTypeArguments, Type ownerType)
+ {
+ this.rawType = rawType;
+ this.actualTypeArguments = actualTypeArguments;
+ this.ownerType = ownerType;
+ }
+
+ public Type getRawType()
+ {
+ return rawType;
+ }
+
+ public Type[] getActualTypeArguments()
+ {
+ return actualTypeArguments;
+ }
+
+ public Type getOwnerType()
+ {
+ return ownerType;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (!(obj instanceof ParameterizedType))
+ return false;
+
+ ParameterizedType other = (ParameterizedType) obj;
+ return rawType.equals(other.getRawType())
+ && Arrays.equals(actualTypeArguments, other.getActualTypeArguments())
+ && (ownerType == null ? other.getOwnerType() == null : ownerType.equals(other
+ .getOwnerType()));
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result = rawType.hashCode() ^ Arrays.hashCode(actualTypeArguments);
+ if (ownerType != null)
+ result ^= ownerType.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ String clazz = rawType.getName();
+
+ if (ownerType != null)
+ {
+ sb.append(Generics.getTypeName(ownerType)).append('.');
+
+ String prefix = (ownerType instanceof ParameterizedType) ? ((Class>) ((ParameterizedType) ownerType)
+ .getRawType()).getName() + '$'
+ : ((Class>) ownerType).getName() + '$';
+ if (clazz.startsWith(prefix))
+ clazz = clazz.substring(prefix.length());
+ }
+ sb.append(clazz);
+
+ if (actualTypeArguments.length != 0)
+ {
+ sb.append('<');
+ for (int i = 0; i < actualTypeArguments.length; i++)
+ {
+ Type arg = actualTypeArguments[i];
+ if (i != 0)
+ sb.append(", ");
+ sb.append(Generics.getTypeName(arg));
+ }
+ sb.append('>');
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/TypeToken.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/TypeToken.java
new file mode 100644
index 00000000..e0c7f063
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/TypeToken.java
@@ -0,0 +1,97 @@
+/*
+ * Copied from Gentyref project http://code.google.com/p/gentyref/
+ * Reformatted and moved to fit package structure
+ */
+
+package com.google.sitebricks.conversion.generics;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+/**
+ * Wrapper around {@link Type}.
+ *
+ * You can use this to create instances of Type for a type known at compile
+ * time.
+ *
+ * For example, to get the Type that represents List<String>:
+ * Type listOfString = new TypeToken<List<String>>(){}.getType();
+ *
+ * @author Wouter Coekaerts
+ *
+ * @param
+ * The type represented by this TypeToken.
+ */
+public abstract class TypeToken
+{
+ private final Type type;
+
+ /**
+ * Constructs a type token.
+ */
+ protected TypeToken()
+ {
+ this.type = extractType();
+ }
+
+ private TypeToken(Type type)
+ {
+ this.type = type;
+ }
+
+ public Type getType()
+ {
+ return type;
+ }
+
+ private Type extractType()
+ {
+ Type t = getClass().getGenericSuperclass();
+ if (!(t instanceof ParameterizedType))
+ {
+ throw new RuntimeException("Invalid TypeToken; must specify type parameters");
+ }
+ ParameterizedType pt = (ParameterizedType) t;
+ if (pt.getRawType() != TypeToken.class)
+ {
+ throw new RuntimeException("Invalid TypeToken; must directly extend TypeToken");
+ }
+ return pt.getActualTypeArguments()[0];
+ }
+
+ /**
+ * Gets type token for the given {@code Class} instance.
+ */
+ public static TypeToken get(Class type)
+ {
+ return new SimpleTypeToken(type);
+ }
+
+ /**
+ * Gets type token for the given {@code Type} instance.
+ */
+ public static TypeToken> get(Type type)
+ {
+ return new SimpleTypeToken(type);
+ }
+
+ private static class SimpleTypeToken extends TypeToken
+ {
+ public SimpleTypeToken(Type type)
+ {
+ super(type);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ return (obj instanceof TypeToken>) && type.equals(((TypeToken>) obj).type);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return type.hashCode();
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/VarMap.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/VarMap.java
new file mode 100644
index 00000000..102b8427
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/VarMap.java
@@ -0,0 +1,94 @@
+/*
+ * Copied from Gentyref project http://code.google.com/p/gentyref/
+ * Reformatted and moved to fit package structure
+ */
+package com.google.sitebricks.conversion.generics;
+
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Mapping between type variables and actual parameters.
+ *
+ * @author Wouter Coekaerts
+ */
+class VarMap
+{
+ private final Map, Type> map = new HashMap, Type>();
+
+ /**
+ * Creates an empty VarMap
+ */
+ VarMap()
+ {
+ }
+
+ void add(TypeVariable> variable, Type value)
+ {
+ map.put(variable, value);
+ }
+
+ void addAll(TypeVariable>[] variables, Type[] values)
+ {
+ assert variables.length == values.length;
+ for (int i = 0; i < variables.length; i++)
+ {
+ map.put(variables[i], values[i]);
+ }
+ }
+
+ VarMap(TypeVariable>[] variables, Type[] values)
+ {
+ addAll(variables, values);
+ }
+
+ Type map(Type type)
+ {
+ if (type instanceof Class>)
+ {
+ return type;
+ }
+ else if (type instanceof TypeVariable>)
+ {
+ assert map.containsKey(type);
+ return map.get(type);
+ }
+ else if (type instanceof ParameterizedType)
+ {
+ ParameterizedType pType = (ParameterizedType) type;
+ return new ParameterizedTypeImpl((Class>) pType.getRawType(), map(pType
+ .getActualTypeArguments()), pType.getOwnerType() == null ? pType.getOwnerType()
+ : map(pType.getOwnerType()));
+ }
+ else if (type instanceof WildcardType)
+ {
+ WildcardType wType = (WildcardType) type;
+ return new WildcardTypeImpl(map(wType.getUpperBounds()), map(wType.getLowerBounds()));
+ }
+ else if (type instanceof GenericArrayType)
+ {
+ return GenericArrayTypeImpl.createArrayType(map(((GenericArrayType) type)
+ .getGenericComponentType()));
+ }
+ else
+ {
+ throw new RuntimeException("not implemented: mapping " + type.getClass() + " (" + type
+ + ")");
+ }
+ }
+
+ Type[] map(Type[] types)
+ {
+ Type[] result = new Type[types.length];
+ for (int i = 0; i < types.length; i++)
+ {
+ result[i] = map(types[i]);
+ }
+ return result;
+ }
+}
diff --git a/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/WildcardTypeImpl.java b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/WildcardTypeImpl.java
new file mode 100644
index 00000000..6e20fe64
--- /dev/null
+++ b/sitebricks-converter/src/main/java/com/google/sitebricks/conversion/generics/WildcardTypeImpl.java
@@ -0,0 +1,67 @@
+/*
+ * Copied from Gentyref project http://code.google.com/p/gentyref/
+ * Reformatted and moved to fit package structure
+ */
+package com.google.sitebricks.conversion.generics;
+
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
+import java.util.Arrays;
+
+class WildcardTypeImpl implements WildcardType
+{
+ private final Type[] upperBounds;
+ private final Type[] lowerBounds;
+
+ public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds)
+ {
+ if (upperBounds.length == 0)
+ throw new IllegalArgumentException(
+ "There must be at least one upper bound. For an unbound wildcard, the upper bound must be Object");
+ this.upperBounds = upperBounds;
+ this.lowerBounds = lowerBounds;
+ }
+
+ public Type[] getUpperBounds()
+ {
+ return upperBounds;
+ }
+
+ public Type[] getLowerBounds()
+ {
+ return lowerBounds;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (!(obj instanceof WildcardType))
+ return false;
+ WildcardType other = (WildcardType) obj;
+ return Arrays.equals(lowerBounds, other.getLowerBounds())
+ && Arrays.equals(upperBounds, other.getUpperBounds());
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Arrays.hashCode(lowerBounds) ^ Arrays.hashCode(upperBounds);
+ }
+
+ @Override
+ public String toString()
+ {
+ if (lowerBounds.length > 0)
+ {
+ return "? super " + Generics.getTypeName(lowerBounds[0]);
+ }
+ else if (upperBounds[0] == Object.class)
+ {
+ return "?";
+ }
+ else
+ {
+ return "? extends " + Generics.getTypeName(upperBounds[0]);
+ }
+ }
+}
diff --git a/sitebricks-converter/src/test/java/com/google/sitebricks/conversion/MvelTypeConverterTest.java b/sitebricks-converter/src/test/java/com/google/sitebricks/conversion/MvelTypeConverterTest.java
new file mode 100644
index 00000000..21638623
--- /dev/null
+++ b/sitebricks-converter/src/test/java/com/google/sitebricks/conversion/MvelTypeConverterTest.java
@@ -0,0 +1,14 @@
+package com.google.sitebricks.conversion;
+
+import org.testng.annotations.Test;
+
+import com.google.sitebricks.conversion.MvelTypeConverter;
+
+public class MvelTypeConverterTest
+{
+ @Test
+ public void simpleConversions() {
+ MvelTypeConverter converter = new MvelTypeConverter();
+ assert converter.convert(45, String.class).equals("45");
+ }
+}
diff --git a/sitebricks-converter/src/test/java/com/google/sitebricks/conversion/StandardTypeConverterTest.java b/sitebricks-converter/src/test/java/com/google/sitebricks/conversion/StandardTypeConverterTest.java
new file mode 100644
index 00000000..615959c8
--- /dev/null
+++ b/sitebricks-converter/src/test/java/com/google/sitebricks/conversion/StandardTypeConverterTest.java
@@ -0,0 +1,97 @@
+package com.google.sitebricks.conversion;
+
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.inject.Binder;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.multibindings.Multibinder;
+import com.google.sitebricks.conversion.DateConverters.DateStringConverter;
+
+/**
+ * @author JRodriguez
+ * @author John Patterson (jdpatterson@gmail.com)
+ */
+public class StandardTypeConverterTest {
+
+ private TypeConverter converter;
+
+ // a very weird date format
+ private String format = "ddd MM yy-EE a";
+
+ @BeforeTest
+ public void setup() {
+
+
+ Injector injector = Guice.createInjector(new Module() {
+ @Override
+ public void configure(Binder binder) {
+ //
+ // If the DateStringConverter is not added here first then the tests fail...
+ // There needs to be some way to override converters in a sane way.
+ //
+ Multibinder converters = Multibinder.newSetBinder(binder, Converter.class);
+ converters.addBinding().toInstance(new DateStringConverter(format));
+ ConverterUtils.createConverterMultibinder(converters);
+ }
+ });
+
+ converter = injector.getInstance(StandardTypeConverter.class);
+ }
+
+ @Test
+ public void stringToPrimitive() {
+ Integer answer = converter.convert("42", Integer.class);
+ assert answer == 42;
+ }
+
+ @Test
+ public void numbers() {
+ BigDecimal answer = converter.convert(42, BigDecimal.class);
+ assert answer.intValue() == 42;
+ }
+
+ @Test
+ public void dateToString() {
+ SimpleDateFormat sdf = new SimpleDateFormat (format);
+ Date date = new Date();
+ String answer = converter.convert(date, String.class);
+ assert answer.equals(sdf.format(date));
+ }
+
+ @Test
+ public void stringToDate() {
+ SimpleDateFormat sdf = new SimpleDateFormat(format);
+ Date original = new Date();
+ String expected = sdf.format(original);
+ Date converted = converter.convert(expected, Date.class);
+ String actual = sdf.format(converted);
+ assert actual.equals(expected);
+ }
+
+ @Test
+ public void calendarToString() {
+ SimpleDateFormat sdf = new SimpleDateFormat(format);
+ Calendar calendar = Calendar.getInstance();
+ String answer = converter.convert(calendar, String.class);
+ String expected = sdf.format(calendar.getTime());
+ System.out.println( ">> " + answer );
+ System.out.println( ">> " + expected );
+ assert answer.equals(expected);
+ }
+
+ @Test
+ public void stringToCalendar() {
+ SimpleDateFormat sdf = new SimpleDateFormat(format);
+ Calendar calendar = Calendar.getInstance();
+ Calendar answer = converter.convert(sdf.format(calendar.getTime()), Calendar.class);
+ assert sdf.format(answer.getTime()).equals(sdf.format(calendar.getTime()));
+ }
+}
diff --git a/sitebricks-converter/src/test/java/com/google/sitebricks/conversion/TestTypeConverter.java b/sitebricks-converter/src/test/java/com/google/sitebricks/conversion/TestTypeConverter.java
new file mode 100644
index 00000000..bb400a8c
--- /dev/null
+++ b/sitebricks-converter/src/test/java/com/google/sitebricks/conversion/TestTypeConverter.java
@@ -0,0 +1,15 @@
+package com.google.sitebricks.conversion;
+
+import java.lang.reflect.Type;
+
+import com.google.sitebricks.conversion.TypeConverter;
+
+/**
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+public class TestTypeConverter implements TypeConverter {
+ @SuppressWarnings("unchecked")
+ public T convert(Object raw, Type type) {
+ return (T) this;
+ }
+}
diff --git a/sitebricks-jetty-archetype/pom.xml b/sitebricks-jetty-archetype/pom.xml
new file mode 100644
index 00000000..a5fbd730
--- /dev/null
+++ b/sitebricks-jetty-archetype/pom.xml
@@ -0,0 +1,39 @@
+
+
+ 4.0.0
+
+ com.google.sitebricks
+ sitebricks-parent
+ 0.8.5
+
+ sitebricks-jetty-archetype
+ Sitebricks :: Jetty Archetype
+
+
+
+ com.google.sitebricks
+ sitebricks
+
+
+ org.mortbay.jetty
+ jetty
+
+
+ org.mortbay.jetty
+ jetty-util
+
+
+ org.mortbay.jetty
+ servlet-api-2.5
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
diff --git a/sitebricks-jetty-archetype/src/main/java/info/sitebricks/example/AppConfig.java b/sitebricks-jetty-archetype/src/main/java/info/sitebricks/example/AppConfig.java
new file mode 100644
index 00000000..ba4ff157
--- /dev/null
+++ b/sitebricks-jetty-archetype/src/main/java/info/sitebricks/example/AppConfig.java
@@ -0,0 +1,29 @@
+package info.sitebricks.example;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.servlet.GuiceServletContextListener;
+import com.google.sitebricks.SitebricksModule;
+import info.sitebricks.example.web.HomePage;
+
+/**
+ * The main configuration for a sitebricks servlet app. This class is typically
+ * registered as a <listener> in web.xml.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+public class AppConfig extends GuiceServletContextListener {
+ @Override
+ protected Injector getInjector() {
+ return Guice.createInjector(new SitebricksModule() {
+ @Override
+ protected void configureSitebricks() {
+
+ // Tell Sitebricks to scan the contents of package info.sitebricks.example.web
+ // and ALL of its child packages for bricks, pages and other sitebricks artifacts.
+ scan(HomePage.class.getPackage());
+
+ }
+ });
+ }
+}
diff --git a/sitebricks-jetty-archetype/src/main/java/info/sitebricks/example/Main.java b/sitebricks-jetty-archetype/src/main/java/info/sitebricks/example/Main.java
new file mode 100644
index 00000000..6fd385e4
--- /dev/null
+++ b/sitebricks-jetty-archetype/src/main/java/info/sitebricks/example/Main.java
@@ -0,0 +1,25 @@
+package info.sitebricks.example;
+
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.webapp.WebAppContext;
+
+/**
+ * Main method kicks off the Jetty servlet container pointing at our Sitebricks webapp
+ * in source code.
+ *
+ * You should run this from the sitebricks-jetty-archetype directory or appropriately change
+ * the directory specified ("src/main/resources" by default) below.
+ *
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ */
+public class Main {
+ private static final int PORT = 8080;
+
+ public static void main(String... args) throws Exception {
+ Server server = new Server(PORT);
+ server.addHandler(new WebAppContext("src/main/resources", "/"));
+
+ server.start();
+ server.join();
+ }
+}
diff --git a/sitebricks-jetty-archetype/src/main/java/info/sitebricks/example/web/HomePage.java b/sitebricks-jetty-archetype/src/main/java/info/sitebricks/example/web/HomePage.java
new file mode 100644
index 00000000..3d90c39d
--- /dev/null
+++ b/sitebricks-jetty-archetype/src/main/java/info/sitebricks/example/web/HomePage.java
@@ -0,0 +1,32 @@
+package info.sitebricks.example.web;
+
+import com.google.sitebricks.At;
+import com.google.sitebricks.Visible;
+import com.google.sitebricks.http.Get;
+
+/**
+ * The home page that our users will see at the top level URI "/".
+ *
+ * This page is created once per request and has "no scope" in Guice
+ * terminology. See the Guice wiki
+ * for details.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+@At("/")
+public class HomePage {
+
+ @Visible
+ String message;
+
+ @Get
+ void showHome() {
+ // This is where you would normally fetch stuff from a database, for example.
+ message = "Hello from Sitebricks!";
+ }
+
+ public boolean getShouldShow() {
+ // Always show our message.
+ return true;
+ }
+}
diff --git a/sitebricks-jetty-archetype/src/main/resources/HomePage.html b/sitebricks-jetty-archetype/src/main/resources/HomePage.html
new file mode 100644
index 00000000..47b111dc
--- /dev/null
+++ b/sitebricks-jetty-archetype/src/main/resources/HomePage.html
@@ -0,0 +1,14 @@
+
+
+
+ Sitebricks :: Servlet/Jetty Archetype
+
+
+
+
+ @ShowIf(shouldShow)
+ Message from the app: ${message}
+
+
+
+
\ No newline at end of file
diff --git a/sitebricks-jetty-archetype/src/main/resources/WEB-INF/web.xml b/sitebricks-jetty-archetype/src/main/resources/WEB-INF/web.xml
new file mode 100644
index 00000000..99927f71
--- /dev/null
+++ b/sitebricks-jetty-archetype/src/main/resources/WEB-INF/web.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+ webFilter
+ com.google.inject.servlet.GuiceFilter
+
+
+
+ webFilter
+ /*
+
+
+
+ info.sitebricks.example.AppConfig
+
+
+
diff --git a/sitebricks-mail/pom.xml b/sitebricks-mail/pom.xml
new file mode 100644
index 00000000..def65551
--- /dev/null
+++ b/sitebricks-mail/pom.xml
@@ -0,0 +1,81 @@
+
+
+ 4.0.0
+
+ com.google.sitebricks
+ sitebricks-parent
+ 0.8.5
+
+ sitebricks-mail
+ Sitebricks :: Mail Client
+
+
+
+ jboss-maven2-public-repository
+ http://repository.jboss.org/nexus/content/groups/public-jboss/
+
+ false
+
+
+
+
+
+
+ com.google.sitebricks
+ sitebricks
+ 0.8.5
+
+
+ org.jboss.netty
+ netty
+ 3.2.4.Final
+
+
+ org.apache.james
+ james-server-imapserver
+ 3.0-M2
+
+
+ org.slf4j
+ slf4j-api
+ 1.5.5
+
+
+ ch.qos.logback
+ logback-classic
+ ${ch.qos.logback.version}
+
+
+ ch.qos.logback
+ logback-core
+ ${ch.qos.logback.version}
+
+
+ org.testng
+ testng
+ 5.8
+ jdk15
+ test
+
+
+
+
+ sitebricks-mail
+
+
+ src/test/resources
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 1.6
+ 1.6
+
+
+
+
+
+
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/CommandCompletion.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/CommandCompletion.java
new file mode 100644
index 00000000..a24f35ee
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/CommandCompletion.java
@@ -0,0 +1,51 @@
+package com.google.sitebricks.mail;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ValueFuture;
+import com.google.sitebricks.mail.imap.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+/**
+ * A generic command completion listener that aggregates incoming messages
+ * until it forms a complete response to an issued command.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+class CommandCompletion {
+ private static final Logger log = LoggerFactory.getLogger(CommandCompletion.class);
+ private final ValueFuture valueFuture;
+ private final List value = Lists.newArrayList();
+ private final Long sequence;
+ private final Command command;
+
+ @SuppressWarnings("unchecked") // Ugly gunk needed to prevent generics from spewing everywhere
+ public CommandCompletion(Command command, Long sequence, ValueFuture> valueFuture) {
+ this.valueFuture = (ValueFuture) valueFuture;
+ this.sequence = sequence;
+ this.command = command;
+ }
+
+ public boolean complete(String message) {
+ String[] pieces = message.split("[ ]+", 2);
+
+ String status = pieces[1].toLowerCase();
+ if (status.startsWith("ok") && status.contains("success")) {
+ // Ensure sequencing was correct.
+ if (!Long.valueOf(pieces[0]).equals(sequence)) {
+ log.error("Sequencing incorrect, expected {} but was {} ", sequence, pieces[0]);
+ }
+
+ // Give it the final message and then process the data.
+ value.add(pieces[1]);
+ valueFuture.set(command.extract(value));
+ return true;
+ }
+
+ value.add(pieces[1]);
+
+ return false;
+ }
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/FolderObserver.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/FolderObserver.java
new file mode 100644
index 00000000..50645d89
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/FolderObserver.java
@@ -0,0 +1,21 @@
+package com.google.sitebricks.mail;
+
+/**
+ * Listens for IMAP folder events such as new mail arriving.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+public interface FolderObserver {
+ /**
+ * New mail arrived in this folder. The client should now
+ * check for new MessageStatuses.
+ */
+ void onMailAdded();
+
+ /**
+ * Existing mail was expunged from this folder. This could
+ * happen via other clients or the activation of server-side
+ * filters for example.
+ */
+ void onMailRemoved();
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/Mail.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/Mail.java
new file mode 100644
index 00000000..9ab3e7c5
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/Mail.java
@@ -0,0 +1,24 @@
+package com.google.sitebricks.mail;
+
+import com.google.inject.ImplementedBy;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+@ImplementedBy(SitebricksMail.class)
+public interface Mail {
+ AuthBuilder clientOf(String host, int port);
+
+ public enum Auth { PLAIN, SSL, OAUTH }
+
+ public static interface AuthBuilder {
+ AuthBuilder timeout(long amount, TimeUnit unit);
+
+ AuthBuilder executors(ExecutorService bossPool, ExecutorService workerPool);
+
+ MailClient connect(Auth authType, String username, String password);
+ }
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClient.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClient.java
new file mode 100644
index 00000000..bcc76a0f
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClient.java
@@ -0,0 +1,87 @@
+package com.google.sitebricks.mail;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.sitebricks.mail.imap.Folder;
+import com.google.sitebricks.mail.imap.FolderStatus;
+import com.google.sitebricks.mail.imap.Message;
+import com.google.sitebricks.mail.imap.MessageStatus;
+
+import java.util.List;
+
+/**
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+public interface MailClient {
+ /**
+ * Connects to the IMAP server logs in with the given credentials.
+ */
+ void connect();
+
+ /**
+ * Logs out of the current IMAP session and releases all resources, including
+ * executor services.
+ */
+ void disconnect();
+
+ List capabilities();
+
+ ListenableFuture> listFolders();
+
+ ListenableFuture statusOf(String folder);
+
+ /**
+ * Opens a logical 'session' to the given folder name. This method must be called
+ * prior to using many of the in-folder methods on this API.
+ */
+ ListenableFuture open(String folder);
+
+ /**
+ * Returns a list of message headers in the given folder, between {@code start}
+ * and {@code end}. The start is a 1-based index into the current session's "view"
+ * of an IMAP folder. The message numbers are guaranteed not to change UNLESS
+ * new mail is added or removed during a session (listen for this using the
+ * {@link #watch(Folder, FolderObserver)} method.
+ *
+ * The returned list is in ascending order (start : end) determined by the IMAP
+ * server (the protocol requires chronological descending order--recent : old).
+ *
+ * The messages returned in the list are lightweight {@link MessageStatus} objects
+ * which only contain cursory information and some metadata about a message,
+ * including the subject. This is useful to quickly obtain a list of messages
+ * whose bodies can be fetched later with the more comprehensive
+ * {@link #fetch(Folder, int, int)} method.
+ *
+ * NOTE: you must call {@link #open(String)} first.
+ */
+ ListenableFuture> list(Folder folder, int start, int end);
+
+ /**
+ * Similar to {@link #list(Folder, int, int)} but fetches the entire message
+ * instead of merely a header. Runs a bit slower as a result.
+ *
+ * This returns the complete details of an email message. Prefer {@link #list(Folder, int, int)}
+ * for fetching just subjects/status info as this method can be slower
+ * for messages with large bodies.
+ *
+ * NOTE: you must call {@link #open(String)} first.
+ */
+ public ListenableFuture> fetch(Folder folder, int start, int end);
+
+ /**
+ *
+ *
+ * NOTE: you must call {@link #open(String)} first.
+ */
+ void watch(Folder folder, FolderObserver observer);
+
+ /**
+ * Stops watching a folder if one was currently being watched (otherwise
+ * a noop). Events from 'IDLEing' will immediately stop when this method
+ * returns and the registered {@link FolderObserver} will be forgotten.
+ *
+ * Note the subtle point that this method (though it doesn't block) will
+ * immediately stop firing events to its FolderObserver. This happens
+ * even before IDLEing ceases on the server.
+ */
+ void unwatch();
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClientConfig.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClientConfig.java
new file mode 100644
index 00000000..7dae78d1
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClientConfig.java
@@ -0,0 +1,49 @@
+package com.google.sitebricks.mail;
+
+import com.google.sitebricks.mail.Mail.Auth;
+
+/**
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+class MailClientConfig {
+ private final String host;
+ private final int port;
+ private final Auth authType;
+ private final String username;
+ private final String password;
+ private final long timeout;
+
+ public MailClientConfig(String host, int port, Auth authType, String username, String password,
+ long timeout) {
+ this.host = host;
+ this.port = port;
+ this.authType = authType;
+ this.username = username;
+ this.password = password;
+ this.timeout = timeout;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public Auth getAuthType() {
+ return authType;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public long getTimeout() {
+ return timeout;
+ }
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClientHandler.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClientHandler.java
new file mode 100644
index 00000000..7ac467f8
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClientHandler.java
@@ -0,0 +1,109 @@
+package com.google.sitebricks.mail;
+
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ExceptionEvent;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.channel.SimpleChannelHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * A command/response handler for a single mail connection/user.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+class MailClientHandler extends SimpleChannelHandler {
+ private static final Logger log = LoggerFactory.getLogger(MailClientHandler.class);
+ public static final String CAPABILITY_PREFIX = "* CAPABILITY";
+
+ private final CountDownLatch loginComplete = new CountDownLatch(2);
+ private volatile boolean isLoggedIn = false;
+ private volatile List capabilities;
+ private volatile FolderObserver observer;
+
+ private final Queue completions = new ConcurrentLinkedQueue();
+
+ public void enqueue(Long sequence, CommandCompletion completion) {
+ completions.add(completion);
+ }
+
+ @Override
+ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
+ String message = e.getMessage().toString();
+ log.debug("Message received [{}] from {}", e.getMessage(), e.getRemoteAddress());
+
+ if (message.startsWith(CAPABILITY_PREFIX)) {
+ this.capabilities = Arrays.asList(
+ message.substring(CAPABILITY_PREFIX.length() + 1).split("[ ]+"));
+ loginComplete.countDown();
+ return;
+ }
+
+ if (!isLoggedIn) {
+ if (message.matches("[.] OK .*@.* \\(Success\\)")) {
+ log.debug("Authentication success.");
+ isLoggedIn = true;
+ loginComplete.countDown();
+ }
+ // TODO handle auth failed
+ return;
+ }
+
+ if (null != observer) {
+ message = message.toLowerCase();
+ if (message.endsWith("exists")) {
+ observer.onMailAdded();
+ return;
+ } else if (message.endsWith("expunge")) {
+ observer.onMailRemoved();
+ return;
+ }
+ }
+
+ complete(message);
+ }
+
+ private void complete(String message) {
+ CommandCompletion completion = completions.peek();
+ if (completion == null) {
+ log.error("Could not find the completion for message {} (Was it ever issued?)", message);
+ return;
+ }
+
+ if (completion.complete(message)) {
+ completions.poll();
+ }
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
+ log.error("Exception caught!", e.getCause());
+ }
+
+ public List getCapabilities() {
+ return capabilities;
+ }
+
+ void awaitLogin() {
+ try {
+ loginComplete.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interruption while awaiting server login", e);
+ }
+ }
+
+ /**
+ * Registers a FolderObserver to receive events happening with a particular
+ * folder. Typically an IMAP IDLE feature. If called multiple times, will
+ * overwrite the currently set observer.
+ */
+ void observe(FolderObserver observer) {
+ this.observer = observer;
+ }
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClientPipelineFactory.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClientPipelineFactory.java
new file mode 100644
index 00000000..d142b0e2
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/MailClientPipelineFactory.java
@@ -0,0 +1,50 @@
+package com.google.sitebricks.mail;
+
+import com.google.sitebricks.mail.Mail.Auth;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.handler.codec.frame.DelimiterBasedFrameDecoder;
+import org.jboss.netty.handler.codec.frame.Delimiters;
+import org.jboss.netty.handler.codec.string.StringDecoder;
+import org.jboss.netty.handler.codec.string.StringEncoder;
+import org.jboss.netty.handler.ssl.SslHandler;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+
+/**
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+class MailClientPipelineFactory implements ChannelPipelineFactory {
+ private final MailClientHandler mailClientHandler;
+ private final MailClientConfig config;
+
+ public MailClientPipelineFactory(MailClientHandler mailClientHandler, MailClientConfig config) {
+ this.mailClientHandler = mailClientHandler;
+ this.config = config;
+ }
+
+ public ChannelPipeline getPipeline() throws Exception {
+ // Create a default pipeline implementation.
+ ChannelPipeline pipeline = Channels.pipeline();
+
+ if (config.getAuthType() == Auth.SSL) {
+ SSLEngine sslEngine = SSLContext.getDefault().createSSLEngine();
+ sslEngine.setUseClientMode(true);
+ SslHandler sslHandler = new SslHandler(sslEngine);
+ sslHandler.setEnableRenegotiation(true);
+ pipeline.addLast("ssl", sslHandler);
+ }
+
+ // Add the text line codec combination first,
+ pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
+ pipeline.addLast("decoder", new StringDecoder());
+ pipeline.addLast("encoder", new StringEncoder());
+
+ // and then business logic.
+ pipeline.addLast("handler", mailClientHandler);
+
+ return pipeline;
+ }
+}
\ No newline at end of file
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/NettyImapClient.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/NettyImapClient.java
new file mode 100644
index 00000000..989532f4
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/NettyImapClient.java
@@ -0,0 +1,205 @@
+package com.google.sitebricks.mail;
+
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ValueFuture;
+import com.google.sitebricks.mail.imap.*;
+import org.jboss.netty.bootstrap.ClientBootstrap;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelFuture;
+import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetSocketAddress;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+class NettyImapClient implements MailClient {
+ private static final Logger log = LoggerFactory.getLogger(NettyImapClient.class);
+
+ private final ExecutorService workerPool;
+ private final ClientBootstrap bootstrap;
+
+ private final MailClientConfig config;
+ private final MailClientHandler mailClientHandler;
+
+ private volatile Channel channel;
+ private final AtomicLong sequence = new AtomicLong();
+
+ private volatile Folder currentFolder = null;
+
+ public NettyImapClient(MailClientPipelineFactory pipelineFactory,
+ MailClientConfig config,
+ MailClientHandler mailClientHandler,
+ ExecutorService bossPool,
+ ExecutorService workerPool) {
+ this.workerPool = workerPool;
+ this.bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(
+ bossPool,
+ workerPool));
+
+ this.config = config;
+ this.mailClientHandler = mailClientHandler;
+ this.bootstrap.setPipelineFactory(pipelineFactory);
+ }
+
+ /**
+ * Connects to the IMAP server logs in with the given credentials.
+ */
+ @Override
+ public void connect() {
+ ChannelFuture future = bootstrap.connect(new InetSocketAddress(config.getHost(),
+ config.getPort()));
+
+ Channel channel = future.awaitUninterruptibly().getChannel();
+ if (!future.isSuccess()) {
+ bootstrap.releaseExternalResources();
+ throw new RuntimeException("Could not connect channel", future.getCause());
+ }
+
+ this.channel = channel;
+ login();
+ }
+
+ private void login() {
+ channel.write(". CAPABILITY\r\n");
+ channel.write(". login " + config.getUsername() + " " + config.getPassword() + "\r\n");
+ mailClientHandler.awaitLogin();
+ }
+
+ /**
+ * Logs out of the current IMAP session and releases all resources, including
+ * executor services.
+ */
+ @Override
+ public void disconnect() {
+ currentFolder = null;
+
+ // Log out of the IMAP Server.
+ channel.write(". logout\n");
+
+ // Shut down all thread pools and exit.
+ channel.close().awaitUninterruptibly(config.getTimeout(), TimeUnit.MILLISECONDS);
+ bootstrap.releaseExternalResources();
+
+ // TODO Shut down thread pools?
+ }
+
+ ChannelFuture send(Command command, String args, ValueFuture valueFuture) {
+ Long seq = sequence.incrementAndGet();
+
+ String commandString = seq + " " + command.toString()
+ + (null == args ? "" : " " + args)
+ + "\r\n";
+ log.debug("Sending {} to server...", commandString);
+
+ // Enqueue command.
+ mailClientHandler.enqueue(seq, new CommandCompletion(command, seq, valueFuture));
+
+ return channel.write(commandString);
+ }
+
+ @Override
+ public List capabilities() {
+ return mailClientHandler.getCapabilities();
+ }
+
+ @Override
+ public ListenableFuture> listFolders() {
+ ValueFuture> valueFuture = ValueFuture.create();
+
+ send(Command.LIST_FOLDERS, "\"[Gmail]\" \"*\"", valueFuture);
+
+ return valueFuture;
+ }
+
+ @Override
+ public ListenableFuture statusOf(String folder) {
+ ValueFuture valueFuture = ValueFuture.create();
+
+ String args = '"' + folder + "\" (UIDNEXT RECENT MESSAGES UNSEEN)";
+ send(Command.FOLDER_STATUS, args, valueFuture);
+
+ return valueFuture;
+ }
+
+ @Override
+ public ListenableFuture open(String folder) {
+ final ValueFuture valueFuture = ValueFuture.create();
+ valueFuture.addListener(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ currentFolder = valueFuture.get();
+ } catch (InterruptedException e) {
+ log.error("Interrupted while attempting to open a folder", e);
+ } catch (ExecutionException e) {
+ log.error("Execution exception while attempting to open a folder", e);
+ }
+ }
+ }, workerPool);
+
+ String args = '"' + folder + "\"";
+ send(Command.FOLDER_OPEN, args, valueFuture);
+
+ return valueFuture;
+ }
+
+ @Override
+ public ListenableFuture> list(Folder folder, int start, int end) {
+ checkCurrentFolder(folder);
+ Preconditions.checkArgument(start <= end, "Start must be <= end");
+ Preconditions.checkArgument(start > 0, "Start must be greater than zero (IMAP uses 1-based " +
+ "indexing)");
+ ValueFuture> valueFuture = ValueFuture.create();
+
+ String args = start + ":" + end + " all";
+ send(Command.FETCH_HEADERS, args, valueFuture);
+
+ return valueFuture;
+ }
+
+ @Override
+ public ListenableFuture> fetch(Folder folder, int start, int end) {
+ checkCurrentFolder(folder);
+ Preconditions.checkArgument(start <= end, "Start must be <= end");
+ Preconditions.checkArgument(start > 0, "Start must be greater than zero (IMAP uses 1-based " +
+ "indexing)");
+ ValueFuture> valueFuture = ValueFuture.create();
+
+ String args = start + ":" + end + " all";
+ send(Command.FETCH_FULL, args, valueFuture);
+
+ return valueFuture;
+ }
+
+ @Override
+ public void watch(Folder folder, FolderObserver observer) {
+ checkCurrentFolder(folder);
+
+ send(Command.IDLE, null, ValueFuture.create());
+
+ mailClientHandler.observe(observer);
+ }
+
+ @Override
+ public void unwatch() {
+ // Stop watching folders.
+ mailClientHandler.observe(null);
+
+ channel.write(". DONE");
+ }
+
+ private void checkCurrentFolder(Folder folder) {
+ Preconditions.checkState(folder.equals(currentFolder), "You must have opened folder %s" +
+ " before attempting to read from it (%s is currently open).", folder.getName(),
+ (currentFolder == null ? "No folder" : currentFolder.getName()));
+ }
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/SitebricksMail.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/SitebricksMail.java
new file mode 100644
index 00000000..09873868
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/SitebricksMail.java
@@ -0,0 +1,65 @@
+package com.google.sitebricks.mail;
+
+import com.google.common.base.Preconditions;
+import com.google.sitebricks.mail.Mail.AuthBuilder;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+class SitebricksMail implements Mail, AuthBuilder {
+ private String host;
+ private int port;
+
+ private long timeout;
+
+ private ExecutorService bossPool;
+ private ExecutorService workerPool;
+
+ @Override
+ public AuthBuilder clientOf(String host, int port) {
+ Preconditions.checkArgument(null != host && !host.isEmpty(),
+ "Must specify a valid hostname");
+ Preconditions.checkArgument(port > 0,
+ "Must specify a valid (non-zero) port");
+ this.host = host;
+ this.port = port;
+ return this;
+ }
+
+ @Override
+ public AuthBuilder timeout(long amount, TimeUnit unit) {
+ this.timeout = unit.convert(amount, TimeUnit.MILLISECONDS);
+ return this;
+ }
+
+ @Override
+ public AuthBuilder executors(ExecutorService bossPool, ExecutorService workerPool) {
+ Preconditions.checkArgument(bossPool != null, "Boss executor cannot be null!");
+ Preconditions.checkArgument(workerPool != null, "Worker executor cannot be null!");
+ this.bossPool = bossPool;
+ this.workerPool = workerPool;
+ return this;
+ }
+
+ @Override
+ public MailClient connect(Auth authType, String username, String password) {
+ if (null == bossPool) {
+ bossPool = Executors.newCachedThreadPool();
+ workerPool = Executors.newCachedThreadPool();
+ }
+
+ MailClientConfig config = new MailClientConfig(host, port, authType, username,
+ password, timeout);
+ MailClientHandler mailClientHandler = new MailClientHandler();
+ MailClient client = new NettyImapClient(new MailClientPipelineFactory(mailClientHandler,
+ config), config, mailClientHandler, bossPool, workerPool);
+
+ // Blocks until connected (timeout specified in config).
+ client.connect();
+ return client;
+ }
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/example b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/example
new file mode 100644
index 00000000..87ff1e9b
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/example
@@ -0,0 +1,20 @@
+("Fri, 8 Apr 2011 23:12:09 -0700"
+ "Get Gmail on your mobile phone"
+ (("Gmail Team" NIL "mail-noreply" "google.com")) from
+ (("Gmail Team" NIL "mail-noreply" "google.com")) sender
+ (("Gmail Team" NIL "mail-noreply" "google.com")) reply-to
+ (("imap test" NIL "telnet.imap" "gmail.com")) to
+ NIL NIL NIL cc/bcc?
+ "")
+
+
+("Tue, 26 Apr 2011 23:02:08 +1000"
+ "Fwd: test push"
+ (("Dhanji R. Prasanna" NIL "dhanji" "gmail.com")) from
+ (("Dhanji R. Prasanna" NIL "dhanji" "gmail.com")) sender
+ (("Dhanji R. Prasanna" NIL "dhanji" "gmail.com")) reply-to
+ ((NIL NIL "telnet.imap" "gmail.com")) to
+ NIL cc
+ NIL bcc
+ "" in-reply-to
+ "")
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Command.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Command.java
new file mode 100644
index 00000000..a75d621d
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Command.java
@@ -0,0 +1,43 @@
+package com.google.sitebricks.mail.imap;
+
+import com.google.common.collect.Maps;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+public enum Command {
+ LIST_FOLDERS("list"),
+ FETCH_FULL("fetch"),
+ FOLDER_STATUS("status"),
+ FOLDER_OPEN("select"),
+ FETCH_HEADERS("fetch"),
+ IDLE("idle"); // IMAP4 IDLE command: http://www.ietf.org/rfc/rfc2177.txt
+
+ private final String commandString;
+ private Command(String commandString) {
+ this.commandString = commandString;
+ }
+
+ private static final Map> dataExtractors;
+ static {
+ dataExtractors = Maps.newHashMap();
+
+ dataExtractors.put(LIST_FOLDERS, new ListFoldersExtractor());
+ dataExtractors.put(FOLDER_STATUS, new FolderStatusExtractor());
+ dataExtractors.put(FOLDER_OPEN, new FolderExtractor());
+ dataExtractors.put(FETCH_HEADERS, new MessageStatusExtractor());
+ }
+
+ @SuppressWarnings("unchecked") // Heterogenous collections are a pita in Java.
+ public D extract(List message) {
+ return (D) dataExtractors.get(this).extract(message);
+ }
+
+ @Override
+ public String toString() {
+ return commandString;
+ }
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Extractor.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Extractor.java
new file mode 100644
index 00000000..a9c3c6a1
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Extractor.java
@@ -0,0 +1,12 @@
+package com.google.sitebricks.mail.imap;
+
+import java.util.List;
+
+/**
+ * A command utility that extracts data for particular commands.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+interface Extractor {
+ D extract(List messages);
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Flag.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Flag.java
new file mode 100644
index 00000000..85cb828e
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Flag.java
@@ -0,0 +1,18 @@
+package com.google.sitebricks.mail.imap;
+
+/**
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+public enum Flag {
+ SEEN,
+ RECENT;
+
+ public static Flag named(String name) {
+ if ("SEEN".equals(name)) {
+ return SEEN;
+ } else if ("RECENT".equals(name)) {
+ return RECENT;
+ }
+ return null;
+ }
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Folder.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Folder.java
new file mode 100644
index 00000000..c2d2e18e
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Folder.java
@@ -0,0 +1,27 @@
+package com.google.sitebricks.mail.imap;
+
+/**
+ * Simple data object that represents an IMAP folder.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+public class Folder {
+ private final String name;
+ private int count;
+
+ public Folder(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ void setCount(int count) {
+ this.count = count;
+ }
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/FolderExtractor.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/FolderExtractor.java
new file mode 100644
index 00000000..4d0b41f5
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/FolderExtractor.java
@@ -0,0 +1,36 @@
+package com.google.sitebricks.mail.imap;
+
+import com.google.common.base.Preconditions;
+
+import java.util.List;
+
+/**
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+class FolderExtractor implements Extractor {
+
+ private static final String SELECTED = "selected";
+
+ @Override
+ public Folder extract(List messages) {
+ String folderName = null;
+ int count = 0;
+ for (String message : messages) {
+ String[] pieces = message.split("[ ]+", 3);
+ if (pieces.length > 1 && "EXISTS".equalsIgnoreCase(pieces[1])) {
+ count = Integer.valueOf(pieces[0]);
+ } else if (message.contains(SELECTED)) {
+ // Extract folder name as given by the server.
+ int left = message.indexOf(pieces[1]) + pieces[1].length();
+ folderName = message.substring(left, message.indexOf(SELECTED)).trim();
+ }
+ }
+
+ Preconditions.checkState(null != folderName, "Error in IMAP protocol, " +
+ "could not detect folder name");
+
+ Folder folder = new Folder(folderName);
+ folder.setCount(count);
+ return folder;
+ }
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/FolderStatus.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/FolderStatus.java
new file mode 100644
index 00000000..060eabca
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/FolderStatus.java
@@ -0,0 +1,55 @@
+package com.google.sitebricks.mail.imap;
+
+/**
+ * Some metadata about an IMAP folder.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+public class FolderStatus {
+ private int messages;
+ private int unseen;
+ private int recent;
+ private int nextUid;
+
+ void setMessages(int messages) {
+ this.messages = messages;
+ }
+
+ void setUnseen(int unseen) {
+ this.unseen = unseen;
+ }
+
+ void setRecent(int recent) {
+ this.recent = recent;
+ }
+
+ void setNextUid(int nextUid) {
+ this.nextUid = nextUid;
+ }
+
+ public int getMessages() {
+ return messages;
+ }
+
+ public int getUnseen() {
+ return unseen;
+ }
+
+ public int getRecent() {
+ return recent;
+ }
+
+ public int getNextUid() {
+ return nextUid;
+ }
+
+ @Override
+ public String toString() {
+ return "FolderStatus{" +
+ "messages=" + messages +
+ ", unseen=" + unseen +
+ ", recent=" + recent +
+ ", nextUid=" + nextUid +
+ '}';
+ }
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/FolderStatusExtractor.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/FolderStatusExtractor.java
new file mode 100644
index 00000000..836205ff
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/FolderStatusExtractor.java
@@ -0,0 +1,42 @@
+package com.google.sitebricks.mail.imap;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+class FolderStatusExtractor implements Extractor {
+ private static final Pattern PARENS = Pattern.compile("([(].*[)])");
+
+ @Override
+ public FolderStatus extract(List messages) {
+ FolderStatus status = new FolderStatus();
+
+ // There should generally only be 1.
+ for (String message : messages) {
+ Matcher matcher = PARENS.matcher(message);
+ if (matcher.find()) {
+ String group = matcher.group(1);
+
+ // Strip parens.
+ group = group.substring(1, group.length() - 1);
+ String[] pieces = group.split("[ ]+");
+ for (int i = 0; i < pieces.length; i += 2) {
+ String piece = pieces[i];
+ if ("MESSAGES".equalsIgnoreCase(piece)) {
+ status.setMessages(Integer.valueOf(pieces[i + 1]));
+ } else if ("UNSEEN".equalsIgnoreCase(piece)) {
+ status.setUnseen(Integer.valueOf(pieces[i + 1]));
+ } else if ("RECENT".equalsIgnoreCase(piece)) {
+ status.setRecent(Integer.valueOf(pieces[i + 1]));
+ } else if ("UIDNEXT".equalsIgnoreCase(piece)) {
+ status.setNextUid(Integer.valueOf(pieces[i + 1]));
+ }
+ }
+ }
+ }
+ return status;
+ }
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/ListFoldersExtractor.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/ListFoldersExtractor.java
new file mode 100644
index 00000000..f9355244
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/ListFoldersExtractor.java
@@ -0,0 +1,40 @@
+package com.google.sitebricks.mail.imap;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+class ListFoldersExtractor implements Extractor> {
+ private static final Pattern QUOTES = Pattern.compile("(\".*\")");
+ private static final String ROOT_PREFIX = "\"/\"";
+
+ @Override
+ public List extract(List messages) {
+ ImmutableList.Builder builder = ImmutableList.builder();
+ for (String message : messages) {
+ Matcher matcher = QUOTES.matcher(message);
+ if (matcher.find()) {
+ String group = matcher.group(1);
+
+ if (group.startsWith(ROOT_PREFIX)) {
+ group = group.substring(ROOT_PREFIX.length()).trim();
+ }
+
+ // Strip quotes.
+ if (group.startsWith("\"")) {
+ group = group.substring(1, group.length() - 1);
+ }
+
+ // Generally remove leading "/" and stripquotes
+ builder.add(group);
+ }
+ }
+
+ return builder.build();
+ }
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Message.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Message.java
new file mode 100644
index 00000000..1c6a0d85
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/Message.java
@@ -0,0 +1,11 @@
+package com.google.sitebricks.mail.imap;
+
+/**
+ * Represents a complete IMAP message with all body parts materialized
+ * and decoded as appropriate (for example, non-UTF8 encodings are re-encoded
+ * into UTF8 for raw and rich text).
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+public class Message {
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/MessageExtractor.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/MessageExtractor.java
new file mode 100644
index 00000000..c9e9b71e
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/MessageExtractor.java
@@ -0,0 +1,69 @@
+package com.google.sitebricks.mail.imap;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Extracts a Message from a complete IMAP fetch. Specifically
+ * a "fetch full" command which comes back with subject, sender, uid
+ * internaldate and rfc822.size (length) and all body parts.
+ *
+ * This is the more robust form of {@link MessageStatus}, which should
+ * be preferred for fetching just subjects/status info as this extraction
+ * mechanism can be slower for messages with large bodies.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+class MessageExtractor implements Extractor> {
+ private static final String ENVELOPE_PREFIX = "(ENVELOPE ";
+ private static final String INTERNALDATE = "INTERNALDATE";
+
+ @Override
+ public List extract(List messages) {
+ List statuses = Lists.newArrayList();
+ for (String message : messages) {
+ System.out.println(message);
+ String[] split = message.split("[ ]+", 3);
+
+ // Only parse Fetch responses.
+ if (split.length > 1 && "FETCH".equalsIgnoreCase(split[1])) {
+ // Strip the "XX FETCH" sequence prefix first.
+// statuses.add(parseEnvelope(split[2]));
+ }
+ }
+
+ return statuses;
+ }
+
+
+ private static List tokenize(String message) {
+ List pieces = Lists.newArrayList();
+ char[] chars = message.toCharArray();
+ boolean inString = false;
+ StringBuilder token = new StringBuilder();
+ for (int i = 0; i < chars.length; i++) {
+ char c = chars[i];
+ if (c == '"') {
+
+ // Close of string, bake this token.
+ if (inString) {
+ pieces.add(token.toString().trim());
+ token = new StringBuilder();
+ inString = false;
+ } else
+ inString = true;
+
+ continue;
+ }
+
+ // Skip parentheticals
+ if (!inString && (c == '(' || c == ')')) {
+ continue;
+ }
+
+ token.append(c);
+ }
+ return pieces;
+ }
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/MessageStatus.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/MessageStatus.java
new file mode 100644
index 00000000..bec7a006
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/MessageStatus.java
@@ -0,0 +1,71 @@
+package com.google.sitebricks.mail.imap;
+
+import java.util.Date;
+import java.util.EnumSet;
+
+/**
+ * Represents a single email message.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+public class MessageStatus {
+ private final String messageUid;
+ private final Date receivedDate;
+ private final Date internalDate;
+ private final String subject;
+ private final EnumSet flags;
+
+ private final String from;
+ private final String sender;
+ private final String replyTo;
+
+ public MessageStatus(String messageUid,
+ Date receivedDate,
+ Date internalDate,
+ String subject,
+ EnumSet flags,
+ String from, String sender, String replyTo) {
+ this.messageUid = messageUid;
+ this.receivedDate = receivedDate;
+ this.internalDate = internalDate;
+ this.subject = subject;
+ this.flags = flags;
+ this.from = from;
+ this.sender = sender;
+ this.replyTo = replyTo;
+ }
+
+ public String getMessageUid() {
+ return messageUid;
+ }
+
+ public Date getReceivedDate() {
+ return receivedDate;
+ }
+
+ public Date getInternalDate() {
+ return internalDate;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public EnumSet getFlags() {
+ return flags;
+ }
+
+ @Override
+ public String toString() {
+ return "MessageStatus{" +
+ "messageUid='" + messageUid + '\'' +
+ ", receivedDate=" + receivedDate +
+ ", internalDate=" + internalDate +
+ ", subject='" + subject + '\'' +
+ ", flags=" + flags +
+ ", from='" + from + '\'' +
+ ", sender='" + sender + '\'' +
+ ", replyTo='" + replyTo + '\'' +
+ '}';
+ }
+}
diff --git a/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/MessageStatusExtractor.java b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/MessageStatusExtractor.java
new file mode 100644
index 00000000..c1dee0a6
--- /dev/null
+++ b/sitebricks-mail/src/main/java/com/google/sitebricks/mail/imap/MessageStatusExtractor.java
@@ -0,0 +1,202 @@
+package com.google.sitebricks.mail.imap;
+
+import com.google.common.collect.Lists;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.List;
+
+/**
+ * Extracts a MessageStatus from a partial IMAP fetch. Specifically
+ * a "fetch all" command which comes back with subject, sender, uid
+ * internaldate and rfc822.size (length).
+ *
+ * A more robust form of fetch exists for message body parts which
+ * would handle email body, html mail, attachments, etc.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+class MessageStatusExtractor implements Extractor> {
+ private static final String ENVELOPE_PREFIX = "(ENVELOPE ";
+ private static final String INTERNALDATE = "INTERNALDATE";
+
+ static final String RECEIVED_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss ZZZZZ";
+ static final String INTERNAL_DATE_FORMAT = "dd-MMM-yyyy HH:mm:ss ZZZZZ";
+
+ @Override
+ public List extract(List messages) {
+ List statuses = Lists.newArrayList();
+ for (String message : messages) {
+ String[] split = message.split("[ ]+", 3);
+
+ // Only parse Fetch responses.
+ if (split.length > 1 && "FETCH".equalsIgnoreCase(split[1])) {
+ // Strip the "XX FETCH" sequence prefix first.
+ statuses.add(parseEnvelope(split[2]));
+ }
+ }
+
+ return statuses;
+ }
+
+ private static MessageStatus parseEnvelope(String message) {
+ // Now we have only the envelope remaining.
+ if (!message.startsWith(ENVELOPE_PREFIX)) {
+ // Something's wrong, we can't handle this.
+ throw new RuntimeException("Illegal data format, expecting envelope prefix, " +
+ "found " + message);
+ }
+
+ // Strip envelope wrapper.
+ message = message.substring(ENVELOPE_PREFIX.length(), message.length() - 1);
+
+ // Parse strings or paren-groups.
+ List