TREQ-11: Adding Mustache support to Tiles
git-svn-id: https://svn.apache.org/repos/asf/tiles/framework/trunk/tiles-request@1297703 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index e348d6f..04354f0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,6 +28,7 @@
<module>tiles-request-jsp</module>
<module>tiles-request-freemarker</module>
<module>tiles-request-velocity</module>
+ <module>tiles-request-mustache</module>
</modules>
<build>
<pluginManagement>
diff --git a/tiles-request-mustache/pom.xml b/tiles-request-mustache/pom.xml
new file mode 100644
index 0000000..bf5ccb5
--- /dev/null
+++ b/tiles-request-mustache/pom.xml
@@ -0,0 +1,69 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>tiles-request</artifactId>
+ <groupId>org.apache.tiles</groupId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+ <groupId>org.apache.tiles</groupId>
+ <artifactId>tiles-request-mustache</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <name>Tiles request - Mustache support</name>
+ <description>Tiles request implementation for Mustache templates.</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.tiles</groupId>
+ <artifactId>tiles-request-api</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tiles</groupId>
+ <artifactId>tiles-request-servlet</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.github.spullara.mustache.java</groupId>
+ <artifactId>core</artifactId>
+ <version>0.6.2</version>
+ </dependency>
+ <dependency>
+ <groupId>com.github.spullara.mustache.java</groupId>
+ <artifactId>builder</artifactId>
+ <version>0.6.2</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.5</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.7</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <version>2.5.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymockclassextension</artifactId>
+ <version>2.4</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <repositories>
+ <repository>
+ <id>sonatype-nexus</id>
+ <name>sonatype-nexus</name>
+ <url>https://oss.sonatype.org/content/repositories/releases/</url>
+ </repository>
+ </repositories>
+</project>
diff --git a/tiles-request-mustache/src/main/java/org/apache/tiles/request/MustacheScopeMap.java b/tiles-request-mustache/src/main/java/org/apache/tiles/request/MustacheScopeMap.java
new file mode 100644
index 0000000..e47d11e
--- /dev/null
+++ b/tiles-request-mustache/src/main/java/org/apache/tiles/request/MustacheScopeMap.java
@@ -0,0 +1,76 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tiles.request;
+
+import java.util.Set;
+
+import com.sampullara.mustache.Scope;
+import org.apache.tiles.request.collection.ScopeMap;
+import org.apache.tiles.request.extractor.MustacheScopeExtractor;
+
+
+final class MustacheScopeMap extends ScopeMap {
+
+ /**
+ * The request object to use.
+ */
+ private Scope scope = null;
+
+ /**
+ * Constructor.
+ *
+ * @param request The request object to use.
+ */
+ public MustacheScopeMap(Scope request) {
+ super(new MustacheScopeExtractor(request));
+ this.scope = request;
+ }
+
+ @Override
+ public Object remove(Object key) {
+ return scope.remove(key);
+ }
+
+ @Override
+ public Object put(String key, Object value) {
+ return scope.put(key, value);
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return scope.containsKey(key);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return size() < 1;
+ }
+
+ @Override
+ public Set<String> keySet() {
+ return (Set<String>)(Set<?>)scope.keySet();
+ }
+
+ @Override
+ public int size() {
+ return scope.keySet().size();
+ }
+}
diff --git a/tiles-request-mustache/src/main/java/org/apache/tiles/request/extractor/MustacheScopeExtractor.java b/tiles-request-mustache/src/main/java/org/apache/tiles/request/extractor/MustacheScopeExtractor.java
new file mode 100644
index 0000000..6d9abab
--- /dev/null
+++ b/tiles-request-mustache/src/main/java/org/apache/tiles/request/extractor/MustacheScopeExtractor.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tiles.request.extractor;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Set;
+
+import com.sampullara.mustache.Scope;
+import org.apache.tiles.request.attribute.AttributeExtractor;
+
+
+public final class MustacheScopeExtractor implements AttributeExtractor {
+ private final Scope scope;
+
+ public MustacheScopeExtractor(Scope scope){
+ this.scope = scope;
+ }
+
+ @Override
+ public void removeValue(String key) {
+ scope.remove(key);
+ }
+
+ @Override
+ public Enumeration<String> getKeys() {
+ return (Enumeration<String>) Collections.enumeration((Set<?>)scope.keySet());
+ }
+
+ @Override
+ public Object getValue(String key) {
+ return scope.get(key);
+ }
+
+ @Override
+ public void setValue(String key, Object value) {
+ scope.put(value, value);
+ }
+}
\ No newline at end of file
diff --git a/tiles-request-mustache/src/main/java/org/apache/tiles/request/render/MustacheRenderer.java b/tiles-request-mustache/src/main/java/org/apache/tiles/request/render/MustacheRenderer.java
new file mode 100644
index 0000000..0e60173
--- /dev/null
+++ b/tiles-request-mustache/src/main/java/org/apache/tiles/request/render/MustacheRenderer.java
@@ -0,0 +1,97 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.tiles.request.render;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.sampullara.mustache.MustacheBuilder;
+import com.sampullara.mustache.MustacheException;
+import com.sampullara.mustache.Scope;
+import org.apache.tiles.request.ApplicationContext;
+import org.apache.tiles.request.Request;
+
+/**
+ * The Mustache-specific renderer.
+ *
+ * @version $Rev: 1215006 $ $Date: 2011-12-16 01:30:41 +0100 (Fri, 16 Dec 2011) $
+ * @since 3.0
+ */
+public final class MustacheRenderer implements Renderer {
+
+ private Pattern acceptPattern;
+
+ @Override
+ public void render(String path, Request request) throws IOException {
+ if (path == null) {
+ throw new CannotRenderException("Cannot dispatch a null path");
+ }
+
+ try{
+ new MustacheBuilder()
+ .build(new BufferedReader(new InputStreamReader(getResourceStream(request, path))), path)
+ .execute(request.getWriter(), buildScope(request));
+
+ }catch(MustacheException ex){
+ throw new IOException("failed to MustacheRenderer.render(" + path + ",request)", ex);
+ }
+ }
+
+ private static InputStream getResourceStream(Request request, String path) throws IOException {
+ final ApplicationContext applicationContext = request.getApplicationContext();
+ final URL resource = applicationContext.getResource(path);
+ return resource.openStream();
+ }
+
+ private static Scope buildScope(Request request){
+ Scope scope = null;
+ List<String> availableScopes = request.getAvailableScopes();
+ for(int i = availableScopes.size() -1; i >= 0; --i){
+ scope = null == scope
+ ? new Scope(request.getContext(availableScopes.get(i)))
+ : new Scope(request.getContext(availableScopes.get(i)), scope);
+ }
+ return scope;
+ }
+
+ //@Override
+ public boolean isRenderable(String path, Request request) {
+ if (path == null) {
+ return false;
+ }
+ if (acceptPattern != null) {
+ final Matcher matcher = acceptPattern.matcher(path);
+ return matcher.matches();
+ }
+ return true;
+ }
+
+ public void setAcceptPattern(Pattern acceptPattern) {
+ this.acceptPattern = acceptPattern;
+ }
+}
diff --git a/tiles-request-mustache/src/test/java/org/apache/tiles/request/render/MustacheRendererTest.java b/tiles-request-mustache/src/test/java/org/apache/tiles/request/render/MustacheRendererTest.java
new file mode 100644
index 0000000..8de2298
--- /dev/null
+++ b/tiles-request-mustache/src/test/java/org/apache/tiles/request/render/MustacheRendererTest.java
@@ -0,0 +1,108 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tiles.request.render;
+
+
+import java.io.IOException;
+import java.io.Writer;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.apache.tiles.request.ApplicationContext;
+import org.apache.tiles.request.servlet.ServletRequest;
+import org.junit.Test;
+
+import static org.easymock.classextension.EasyMock.*;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link MustacheRenderer}.
+ *
+ * @version $Rev: 1066788 $ $Date: 2011-02-03 11:49:11 +0000 (Thu, 03 Feb 2011) $
+ */
+public final class MustacheRendererTest {
+
+ /**
+ * Tests {@link MustacheRenderer#render(String, org.apache.tiles.request.Request)}.
+ * @throws IOException If something goes wrong.
+ */
+ @Test
+ public void testRender() throws IOException {
+ ServletRequest request = createMock(ServletRequest.class);
+ Writer writer = createMock(Writer.class);
+ ApplicationContext applicationContext = createMock(ApplicationContext.class);
+ URL resource = getClass().getResource("/test.html");
+
+ Map<String,Object> context = Collections.singletonMap("testKey", (Object)"test value");
+
+ expect(request.getApplicationContext()).andReturn(applicationContext);
+ expect(applicationContext.getResource(isA(String.class))).andReturn(resource).anyTimes();
+ expect(request.getAvailableScopes()).andReturn(Arrays.asList("request", "session", "application"));
+ expect(request.getContext("request")).andReturn(context);
+ expect(request.getContext("session")).andReturn(Collections.<String,Object>emptyMap());
+ expect(request.getContext("application")).andReturn(Collections.<String,Object>emptyMap());
+ expect(request.getWriter()).andReturn(writer);
+ writer.write("test template with test value");
+ writer.flush();
+
+ replay(request, applicationContext, writer);
+ Renderer renderer = new MustacheRenderer();
+ renderer.render("/test.html", request);
+ verify(request, applicationContext, writer);
+ }
+
+ /**
+ * Tests {@link MustacheRenderer#render(String, org.apache.tiles.request.Request)}.
+ * @throws IOException If something goes wrong.
+ */
+ @Test(expected = CannotRenderException.class)
+ public void testRenderException() throws IOException {
+ ServletRequest request = createMock(ServletRequest.class);
+ replay(request);
+ Renderer renderer = new MustacheRenderer();
+ try {
+ renderer.render(null, request);
+ } finally {
+ verify(request);
+ }
+ }
+
+ /**
+ * Test method for
+ * {@link MustacheRenderer#isRenderable(String, org.apache.tiles.request.Request)}
+ * .
+ */
+ @Test
+ public void testIsRenderable() {
+ MustacheRenderer renderer = new MustacheRenderer();
+ final Pattern pattern = Pattern.compile("/.*");
+ renderer.setAcceptPattern(pattern);
+
+ assertTrue(renderer.isRenderable("/my/template.html", null));
+ assertTrue(renderer.isRenderable("/my/template.any", null));
+ assertFalse(renderer.isRenderable("my/template.html", null));
+ assertFalse(renderer.isRenderable(null, null));
+ }
+}
diff --git a/tiles-request-mustache/src/test/resources/test.html b/tiles-request-mustache/src/test/resources/test.html
new file mode 100644
index 0000000..3de397c
--- /dev/null
+++ b/tiles-request-mustache/src/test/resources/test.html
@@ -0,0 +1 @@
+test template with {{testKey}}
\ No newline at end of file