You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/04/01 19:47:16 UTC

[1/4] incubator-freemarker-online-tester git commit: initial commit

Repository: incubator-freemarker-online-tester
Updated Branches:
  refs/heads/master 0fb645b28 -> fc99d2b4f


http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/test/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineResourceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineResourceTest.java b/src/test/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineResourceTest.java
new file mode 100644
index 0000000..ea30902
--- /dev/null
+++ b/src/test/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineResourceTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.resources;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyMap;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import com.kenshoo.freemarker.services.FreeMarkerService;
+import com.kenshoo.freemarker.view.FreeMarkerOnlineView;
+
+import freemarker.core.OutputFormat;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: nir
+ * Date: 4/12/14
+ * Time: 11:23 PM
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class FreeMarkerOnlineResourceTest {
+
+    @InjectMocks
+    FreeMarkerOnlineResource freeMarkerOnlineResultResource;
+
+    @Mock
+    FreeMarkerService freeMarkerService;
+
+    @Test
+    public void testInitialForm() {
+        when(freeMarkerService.calculateTemplateOutput(
+                anyString(), anyMap(), any(OutputFormat.class), any(Locale.class), any(TimeZone.class)))
+                .thenThrow(new AssertionError());
+        FreeMarkerOnlineView view = freeMarkerOnlineResultResource.blankForm();
+        assertEquals(view.getTemplate(), "");
+        assertEquals(view.getDataModel(), "");
+    }
+    
+    @Test
+    public void testPostedBlankForm() {
+        when(freeMarkerService.calculateTemplateOutput(
+                anyString(), anyMap(), any(OutputFormat.class), any(Locale.class), any(TimeZone.class)))
+                .thenThrow(new AssertionError());
+        FreeMarkerOnlineView view = freeMarkerOnlineResultResource.formResult(null, null, null, null, null);
+        assertEquals(view.getTemplate(), "");
+        assertEquals(view.getDataModel(), "");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/test/java/com/kenshoo/freemarker/services/FreeMarkerServiceResponseBuilderTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/com/kenshoo/freemarker/services/FreeMarkerServiceResponseBuilderTest.java b/src/test/java/com/kenshoo/freemarker/services/FreeMarkerServiceResponseBuilderTest.java
new file mode 100644
index 0000000..d30bdec
--- /dev/null
+++ b/src/test/java/com/kenshoo/freemarker/services/FreeMarkerServiceResponseBuilderTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.services;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: nir
+ * Date: 4/13/14
+ * Time: 7:30 AM
+ */
+public class FreeMarkerServiceResponseBuilderTest {
+
+    private static final String RESULT = "Result";
+    
+    private final FreeMarkerServiceResponse.Builder builder = new FreeMarkerServiceResponse.Builder();
+
+    @Test
+    public void testSuccessResult() {
+        FreeMarkerServiceResponse result = builder.buildForSuccess(RESULT, false);
+        assertThat(result.getTemplateOutput(), equalTo(RESULT));
+        assertThat(result.isTemplateOutputTruncated(), is(false));
+        assertThat(result.isSuccesful(), is(true));
+        assertThat(result.getFailureReason(), nullValue());
+    }
+        
+    @Test
+    public void testSuccessTruncatedResult() {
+        FreeMarkerServiceResponse result = builder.buildForSuccess(RESULT, true);
+        assertThat(result.getTemplateOutput(), equalTo(RESULT));
+        assertThat(result.isTemplateOutputTruncated(), is(true));
+        assertThat(result.isSuccesful(), is(true));
+        assertThat(result.getFailureReason(), nullValue());
+    }
+
+    @Test
+    public void testErrorResult() {
+        Throwable failureReason = new Exception();
+        FreeMarkerServiceResponse result = builder.buildForFailure(failureReason);
+        assertThat(result.getTemplateOutput(), nullValue());
+        assertThat(result.isTemplateOutputTruncated(), is(false));
+        assertThat(result.isSuccesful(), is(false));
+        assertThat(result.getFailureReason(), sameInstance(failureReason));
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/test/java/com/kenshoo/freemarker/services/FreeMarkerServiceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/com/kenshoo/freemarker/services/FreeMarkerServiceTest.java b/src/test/java/com/kenshoo/freemarker/services/FreeMarkerServiceTest.java
new file mode 100644
index 0000000..5bd19dd
--- /dev/null
+++ b/src/test/java/com/kenshoo/freemarker/services/FreeMarkerServiceTest.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.services;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import freemarker.core.Environment;
+import freemarker.core.HTMLOutputFormat;
+import freemarker.core.ParseException;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateDirectiveModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: nir
+ * Date: 4/12/14
+ * Time: 11:15 AM
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class FreeMarkerServiceTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(FreeMarkerServiceTest.class); 
+    
+    private static final int MAX_THREADS = 3;
+    private static final int MAX_QUEUE_LENGTH = 2;
+    private static final int MAX_TEMPLATE_EXECUTION_TIME = 1500;
+
+    private static final int BLOCKING_TEST_TIMEOUT = 5000;
+    
+    private static final String TRUNCATION_TEST_TEMPLATE = "12345";
+    
+    @InjectMocks
+    private FreeMarkerService freeMarkerService;
+    
+    @Before
+    public void initializeSpringBeans() {
+        freeMarkerService.setMaxQueueLength(MAX_QUEUE_LENGTH);
+        freeMarkerService.setMaxThreads(MAX_THREADS);
+        freeMarkerService.postConstruct();
+        freeMarkerService.setMaxTemplateExecutionTime(MAX_TEMPLATE_EXECUTION_TIME);
+    }
+
+    @Test
+    public void testCalculationOfATemplateWithNoDataModel() {
+        FreeMarkerServiceResponse serviceResponse = freeMarkerService.calculateTemplateOutput(
+                "test", Collections.<String, Object>emptyMap(), null, null, null);
+        assertThat(serviceResponse.isSuccesful(), is(true));
+        assertThat(serviceResponse.getTemplateOutput(), is("test"));
+    }
+
+    @Test
+    public void testSimpleTemplate() {
+        HashMap<String, Object> dataModel = new HashMap<>();
+        dataModel.put("var1", "val1");
+        String templateSourceCode = "${var1}";
+        FreeMarkerServiceResponse serviceResponse = freeMarkerService.calculateTemplateOutput(
+                templateSourceCode, dataModel, null, null, null);
+        assertThat(serviceResponse.getTemplateOutput(), equalTo("val1"));
+    }
+
+    @Test
+    public void testTemplateWithFewArgsAndOperators() {
+        HashMap<String, Object> dataModel = new HashMap<>();
+        dataModel.put("var1", "val1");
+        dataModel.put("var2", "val2");
+        String template = "${var1?capitalize} ${var2?cap_first}";
+        FreeMarkerServiceResponse serviceResponse = freeMarkerService.calculateTemplateOutput(
+                template, dataModel, null, null, null);
+        assertThat(serviceResponse.getTemplateOutput(), equalTo("Val1 Val2"));
+    }
+
+    @Test
+    public void testOutputFormatParamterMatters() {
+        String template = "${'&'}";
+        {
+            FreeMarkerServiceResponse serviceResponse = freeMarkerService.calculateTemplateOutput(
+                    template, null, null, null, null);
+            assertThat(serviceResponse.getTemplateOutput(), equalTo("&"));
+        }
+        {
+            FreeMarkerServiceResponse serviceResponse = freeMarkerService.calculateTemplateOutput(
+                    template, null, HTMLOutputFormat.INSTANCE, null, null);
+            assertThat(serviceResponse.getTemplateOutput(), equalTo("&amp;"));
+        }
+    }
+
+    @Test
+    public void testLocaleParameterMatters() {
+        String template = "${.locale}";
+        {
+            FreeMarkerServiceResponse serviceResponse = freeMarkerService.calculateTemplateOutput(
+                    template, null, null, new Locale("en", "US"), null);
+            assertThat(serviceResponse.getTemplateOutput(), equalTo("en_US"));
+        }
+        {
+            FreeMarkerServiceResponse serviceResponse = freeMarkerService.calculateTemplateOutput(
+                    template, null, null, new Locale("ru", "RU"), null);
+            assertThat(serviceResponse.getTemplateOutput(), equalTo("ru_RU"));
+        }
+    }
+
+    @Test
+    public void testTimeZoneParameterMatters() {
+        String template = "${" + System.currentTimeMillis() + "?numberToDatetime}";
+        
+        String gmt1Result;
+        {
+            FreeMarkerServiceResponse serviceResponse = freeMarkerService.calculateTemplateOutput(
+                    template, null, null, null, TimeZone.getTimeZone("GMT+01"));
+            gmt1Result = serviceResponse.getTemplateOutput();
+        }
+        
+        String gmt2Result;
+        {
+            FreeMarkerServiceResponse serviceResponse = freeMarkerService.calculateTemplateOutput(
+                    template, null, null, new Locale("ru", "RU"), null);
+            gmt2Result = serviceResponse.getTemplateOutput();
+        }
+        
+        assertThat(gmt1Result, not(equalTo(gmt2Result)));
+    }
+    
+    @Test
+    public void testTemplateWithSyntaxError() {
+        FreeMarkerServiceResponse serviceResponse = freeMarkerService.calculateTemplateOutput(
+                "test ${xx", Collections.<String, Object>emptyMap(), null, null, null);
+        assertThat(serviceResponse.isSuccesful(), is(false));
+        assertThat(serviceResponse.getFailureReason(), instanceOf(ParseException.class));
+    }
+
+    @Test
+    public void testTemplateWithEvaluationError() {
+        FreeMarkerServiceResponse serviceResponse = freeMarkerService.calculateTemplateOutput(
+                "test ${x}", Collections.<String, Object>emptyMap(), null, null, null);
+        assertThat(serviceResponse.isSuccesful(), is(false));
+        assertThat(serviceResponse.getFailureReason(), instanceOf(TemplateException.class));
+    }
+
+    @Test
+    public void testResultAlmostTruncation() {
+        freeMarkerService.setMaxOutputLength(5);
+        FreeMarkerServiceResponse serviceResponse = freeMarkerService.calculateTemplateOutput(
+                TRUNCATION_TEST_TEMPLATE, Collections.<String, Object>emptyMap(), null, null, null);
+        assertThat(serviceResponse.isSuccesful(), is(true));
+        assertThat(serviceResponse.isTemplateOutputTruncated(), is(false));
+        assertThat(serviceResponse.getTemplateOutput(), equalTo(TRUNCATION_TEST_TEMPLATE));
+    }
+
+    @Test
+    public void testResultTruncation() {
+        freeMarkerService.setMaxOutputLength(4);
+        FreeMarkerServiceResponse serviceResponse = freeMarkerService.calculateTemplateOutput(
+                TRUNCATION_TEST_TEMPLATE, Collections.<String, Object>emptyMap(), null, null, null);
+        assertThat(serviceResponse.isSuccesful(), is(true));
+        assertThat(serviceResponse.isTemplateOutputTruncated(), is(true));
+        assertThat(serviceResponse.getTemplateOutput(),
+                startsWith(TRUNCATION_TEST_TEMPLATE.substring(0, freeMarkerService.getMaxOutputLength())));
+        assertThat(serviceResponse.getTemplateOutput().charAt(freeMarkerService.getMaxOutputLength()),
+                not(equalTo(TRUNCATION_TEST_TEMPLATE.charAt(freeMarkerService.getMaxOutputLength()))));
+    }
+    
+    @Test
+    public void testTemplateExecutionTimeout() throws InterruptedException, ExecutionException {
+        freeMarkerService.setMaxTemplateExecutionTime(200);
+        
+        // To avoid blocking the CI server forever without giving error:
+        Future<FreeMarkerServiceResponse> future = Executors.newSingleThreadExecutor().submit(
+                new Callable<FreeMarkerServiceResponse>() {
+        
+                    @Override
+                    public FreeMarkerServiceResponse call() throws Exception {
+                        return freeMarkerService.calculateTemplateOutput(
+                                "<#list 1.. as _></#list>", Collections.<String, Object>emptyMap(), null, null, null);
+                    }
+                    
+                });
+        FreeMarkerServiceResponse serviceResponse;
+        try {
+            serviceResponse = future.get(BLOCKING_TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+        } catch (TimeoutException e) {
+            throw new AssertionError("The template execution wasn't aborted (within the timeout).");
+        }
+        assertThat(serviceResponse.isSuccesful(), is(false));
+        assertThat(serviceResponse.getFailureReason(), instanceOf(TimeoutException.class));
+    }
+    
+    @Test
+    public void testServiceOverburden() throws InterruptedException {
+        final BlockerDirective blocker = new BlockerDirective();
+        final Map<String, BlockerDirective> blockerDataModel = Collections.singletonMap("blocker", blocker);
+        try {
+            // Fill all available task "slots":
+            for (int i = 0; i < MAX_THREADS + MAX_QUEUE_LENGTH; i++) {
+                new Thread(new Runnable() {
+                    @Override
+                    public void run() {
+                        freeMarkerService.calculateTemplateOutput("<@blocker/>", blockerDataModel, null, null, null);                    
+                    }
+                }).start();
+            }
+            
+            // Wait until all template executions has started:
+            synchronized (blocker) {
+                final long startTime = System.currentTimeMillis();
+                while (blocker.getEntered() < MAX_THREADS) {
+                    // To avoid blocking the CI server forever is something goes wrong:
+                    if (System.currentTimeMillis() - startTime > BLOCKING_TEST_TIMEOUT) {
+                        fail("JUnit test timed out");
+                    }
+                    blocker.wait(1000);
+                }
+            }
+            Thread.sleep(200);
+            // Because the others are waiting in the queue, and weren't started:
+            assertThat(blocker.getEntered(), not(greaterThan(MAX_THREADS)));
+            
+            // Souldn't accept on more tasks:
+            try {
+                freeMarkerService.calculateTemplateOutput("<@blocker/>", blockerDataModel, null, null, null);
+                fail("Expected RejectedExecutionException, but nothing was thrown.");
+            } catch (RejectedExecutionException e) {
+                // Expected
+            }
+        } finally {
+            // Ensure that the started threads will end:
+            blocker.release();
+        }
+    }
+    
+    private static final class BlockerDirective implements TemplateDirectiveModel {
+        
+        private int entered;
+        private boolean released;
+
+        public synchronized void release() {
+            released = true;
+            notifyAll();
+        }
+        
+        @Override
+        public synchronized void execute(Environment env, @SuppressWarnings("rawtypes") Map params,
+                TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException {
+            entered++;
+            notifyAll();
+            final long startTime = System.currentTimeMillis();
+            while (!released) {
+                // To avoid blocking the CI server forever is something goes wrong:
+                if (System.currentTimeMillis() - startTime > BLOCKING_TEST_TIMEOUT) {
+                    LOG.error("JUnit test timed out");
+                }
+                try {
+                    wait(1000);
+                } catch (InterruptedException e) {
+                    LOG.error("JUnit test was interrupted");
+                }
+            }
+            LOG.debug("Blocker released");
+        }
+
+        public synchronized int getEntered() {
+            return entered;
+        }
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/test/java/com/kenshoo/freemarker/util/DataModelParserTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/com/kenshoo/freemarker/util/DataModelParserTest.java b/src/test/java/com/kenshoo/freemarker/util/DataModelParserTest.java
new file mode 100644
index 0000000..08f389c
--- /dev/null
+++ b/src/test/java/com/kenshoo/freemarker/util/DataModelParserTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.util;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.math.BigDecimal;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.GregorianCalendar;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import freemarker.template.utility.DateUtil;
+
+public class DataModelParserTest {
+    
+    @Test
+    public void testEmpty() throws DataModelParsingException {
+        assertTrue(DataModelParser.parse("", DateUtil.UTC).isEmpty());
+        assertTrue(DataModelParser.parse(" \n ", DateUtil.UTC).isEmpty());
+    }
+
+    @Test
+    public void testSingleAssignment() throws DataModelParsingException {
+        assertEquals(ImmutableMap.of("n", "v"), DataModelParser.parse("n=v", DateUtil.UTC));
+        assertEquals(ImmutableMap.of("n", "v"), DataModelParser.parse("\n\n\tn\t= v", DateUtil.UTC));
+        assertEquals(ImmutableMap.of("longerN", "longer v"), DataModelParser.parse("longerN=longer v", DateUtil.UTC));
+        assertEquals(ImmutableMap.of("a:b.c-d$@", "foo bar\nbaaz"), DataModelParser.parse("a:b.c-d$@ = foo bar\nbaaz", DateUtil.UTC));
+    }
+
+    @Test
+    public void testNotBlankButHasNoAssignment() {
+        try {
+            DataModelParser.parse("x", DateUtil.UTC);
+            fail();
+        } catch (DataModelParsingException e) {
+            assertThat(e.getMessage(), containsString("must start with an assignment"));
+        }
+    }
+
+    @Test
+    public void testNoLinebreakBeforeEquals() {
+        try {
+            DataModelParser.parse("x\n=y", DateUtil.UTC);
+            fail();
+        } catch (DataModelParsingException e) {
+            assertThat(e.getMessage(), containsString("must start with an assignment"));
+        }
+    }
+
+    @Test
+    public void testMultipleAssignments() throws DataModelParsingException {
+        assertEquals(ImmutableMap.of("n1", "v1", "n2", "v2", "n3", "v3"),
+                DataModelParser.parse("n1=v1\nn2=v2\nn3=v3", DateUtil.UTC));
+        assertEquals(ImmutableMap.of("n1", "v1", "n2", "v2", "n3", "v3"),
+                DataModelParser.parse(" n1 = v1 \r\n\r\n\tn2=v2\nn3 = v3\n\n", DateUtil.UTC));
+        assertEquals(ImmutableMap.of("n1", "=\n=v", "n2", "l1\nl2\n\nl3", "n3", "v3"),
+                DataModelParser.parse("n1==\n=v \n n2=l1\nl2\n\nl3\nn3=v3", DateUtil.UTC));
+    }
+
+    @Test
+    public void testStrings() throws DataModelParsingException {
+        assertEquals(
+                ImmutableMap.of(
+                    "a", "C:\\x",
+                    "b", "foo\nbar",
+                    "c", "foo\t\"bar\"",
+                    "d", "foo\t\"bar\"",
+                    "e", "Foo's"
+                ),
+                DataModelParser.parse(
+                        "a=C:\\x\n"
+                        + "b=foo\nbar\n"
+                        + "c=foo\t\"bar\"\n"
+                        + "d=\"foo\\t\\\"bar\\\"\"\n"
+                        + "e=\"Foo's\"",
+                        DateUtil.UTC));
+        try {
+            DataModelParser.parse("a=\"foo", DateUtil.UTC);
+            fail();
+        } catch (DataModelParsingException e) {
+            assertThat(e.getMessage(), containsString("quoted"));
+        }
+        try {
+            DataModelParser.parse("a='foo'", DateUtil.UTC);
+            fail();
+        } catch (DataModelParsingException e) {
+            assertThat(e.getMessage(), containsString("quoted"));
+        }
+        try {
+            DataModelParser.parse("a=\"\\x\"", DateUtil.UTC);
+            fail();
+        } catch (DataModelParsingException e) {
+            assertThat(e.getMessage(), containsString("escape"));
+        }
+    }
+    
+    @Test
+    public void testBasicNumbers() throws DataModelParsingException {
+        assertEquals(
+                ImmutableMap.of(
+                    "a", BigDecimal.valueOf(1),
+                    "b", BigDecimal.valueOf(1.5),
+                    "c", BigDecimal.valueOf(-1.5),
+                    "d", BigDecimal.valueOf(1.5),
+                    "e", BigDecimal.valueOf(-0.125)
+                ),
+                DataModelParser.parse("a=1\nb=1.5\nc=-1.5\nd=+1.5\ne=-12.5e-2", DateUtil.UTC));
+        try {
+            DataModelParser.parse("a=1,5", DateUtil.UTC);
+            fail();
+        } catch (DataModelParsingException e) {
+            assertThat(e.getMessage(), containsString("Malformed number"));
+            assertThat(e.getMessage(), not(containsString("ISO")));
+        }
+    }
+    
+    @Test
+    public void testSpecialNumbers() throws DataModelParsingException {
+    assertEquals(
+            ImmutableMap.of(
+                "a", Double.NaN,
+                "b", Double.POSITIVE_INFINITY,
+                "c", Double.NEGATIVE_INFINITY,
+                "d", Double.POSITIVE_INFINITY
+            ),
+            DataModelParser.parse("a=NaN\nb=Infinity\nc=-Infinity\nd=+Infinity", DateUtil.UTC));
+    }
+
+    @Test
+    public void testBooleans() throws DataModelParsingException {
+        assertEquals(ImmutableMap.of("a", true, "b", false), DataModelParser.parse("a=true\nb=false", DateUtil.UTC));
+        try {
+            DataModelParser.parse("a=True", DateUtil.UTC);
+            fail();
+        } catch (DataModelParsingException e) {
+            assertThat(e.getMessage(), containsString("true"));
+        }
+    }
+    
+    @Test
+    public void testTemporals() throws DataModelParsingException {
+        final Map<String, Object> dm = DataModelParser.parse("a=2014-02-12T01:02:03Z\nb=2014-02-12\nc=01:02:03Z", DateUtil.UTC);
+        
+        final GregorianCalendar cal = new GregorianCalendar(DateUtil.UTC);
+        cal.clear();
+        
+        cal.set(2014, 1, 12, 1, 2, 3);
+        Timestamp a = new Timestamp(cal.getTimeInMillis());
+        assertThat(dm.get("a"), instanceOf(Timestamp.class));
+        assertEquals(dm.get("a"), a);
+        
+        cal.set(2014, 1, 12, 0, 0, 0);
+        java.sql.Date b = new java.sql.Date(cal.getTimeInMillis());
+        assertThat(dm.get("b"), instanceOf(java.sql.Date.class));
+        assertEquals(dm.get("b"), b);
+        
+        cal.set(1970, 0, 1, 1, 2, 3);
+        Time c = new Time(cal.getTimeInMillis());
+        assertThat(dm.get("c"), instanceOf(Time.class));
+        assertEquals(dm.get("c"), c);
+        
+        try {
+            DataModelParser.parse("a=2012T123", DateUtil.UTC);
+            fail();
+        } catch (DataModelParsingException e) {
+            assertThat(e.getMessage(), containsString("ISO 8601 date-time"));
+        }
+        try {
+            DataModelParser.parse("a=2012-0102", DateUtil.UTC);
+            fail();
+        } catch (DataModelParsingException e) {
+            assertThat(e.getMessage(), containsString("ISO 8601 date"));
+        }
+        try {
+            DataModelParser.parse("a=25:00", DateUtil.UTC);
+            fail();
+        } catch (DataModelParsingException e) {
+            assertThat(e.getMessage(), containsString("ISO 8601 time"));
+        }
+    }
+    
+    @Test
+    public void testMaps() throws DataModelParsingException {
+        final Object map = DataModelParser.parse(
+                "n = {\n"
+                + "\t\"a\": 1,\n"
+                + "\t\"b\": 2\n"
+                + "}",
+                DateUtil.UTC)
+                .get("n");
+        assertEquals(ImmutableMap.of("a", 1, "b", 2), map);
+        assertThat(map, instanceOf(LinkedHashMap.class));
+        try {
+            DataModelParser.parse("n={1:2}", DateUtil.UTC);
+            fail();
+        } catch (DataModelParsingException e) {
+            assertThat(e.getMessage(), containsString("JSON"));
+        }
+    }    
+    
+    @Test
+    public void testLists() throws DataModelParsingException {
+        final Object list = DataModelParser.parse("n=[1, 2]", DateUtil.UTC).get("n");
+        assertEquals(ImmutableList.of(1, 2), list);
+        assertThat(list, instanceOf(List.class));
+        try {
+            DataModelParser.parse("n=[", DateUtil.UTC);
+            fail();
+        } catch (DataModelParsingException e) {
+            assertThat(e.getMessage(), containsString("JSON"));
+        }
+    }
+
+    @Test
+    public void testXML() throws DataModelParsingException {
+        final Object doc = DataModelParser.parse("n=<e xmlns='foo:/bar' a='123'>text</e>", DateUtil.UTC).get("n");
+        assertThat(doc, instanceOf(Document.class));
+        final Node firstChild = ((Document) doc).getFirstChild();
+        assertEquals("e", firstChild.getNodeName());
+        assertEquals("foo:/bar", firstChild.getNamespaceURI());
+        
+        try {
+            DataModelParser.parse("n=<ns:e />", DateUtil.UTC);
+            fail();
+        } catch (DataModelParsingException e) {
+            assertThat(e.getMessage(), containsString("XML"));
+        }
+    }
+    
+    @Test
+    public void testNull() throws DataModelParsingException {
+        assertNull(DataModelParser.parse("n=null", DateUtil.UTC).get("n"));
+        try {
+            DataModelParser.parse("a=NULL", DateUtil.UTC);
+            fail();
+        } catch (DataModelParsingException e) {
+            assertThat(e.getMessage(), containsString("null"));
+        }
+    }
+
+    @Test
+    public void testEmptyValue() throws DataModelParsingException {
+        try {
+            DataModelParser.parse("n=", DateUtil.UTC);
+            fail();
+        } catch (DataModelParsingException e) {
+            assertThat(e.getMessage(), containsString("Empty"));
+        }
+        
+        assertEquals("", DataModelParser.parse("n=\"\"", DateUtil.UTC).get("n"));
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/test/java/com/kenshoo/freemarker/util/LengthLimitedWriterTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/com/kenshoo/freemarker/util/LengthLimitedWriterTest.java b/src/test/java/com/kenshoo/freemarker/util/LengthLimitedWriterTest.java
new file mode 100644
index 0000000..169b39e
--- /dev/null
+++ b/src/test/java/com/kenshoo/freemarker/util/LengthLimitedWriterTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.util;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+import org.junit.Test;
+
+public class LengthLimitedWriterTest {
+
+    private StringWriter wrappedW = new StringWriter();
+    private LengthLimitedWriter w = new LengthLimitedWriter(wrappedW, 5);
+    
+    @Test
+    public void testLimitNotExceeded() throws IOException {
+        w.write("123");
+        w.write("45");
+    }
+
+    @Test
+    public void testLimitExceededWithString() throws IOException {
+        w.write("123");
+        try {
+            w.write("456");
+            fail();
+        } catch (LengthLimitExceededException e) {
+            assertEquals("12345", wrappedW.toString());
+        }
+    }
+
+    @Test
+    public void testLimitExceededWithCharArray() throws IOException {
+        w.write(new char[] { '1', '2', '3' });
+        try {
+            w.write(new char[] { '4', '5', '6' });
+            fail();
+        } catch (LengthLimitExceededException e) {
+            assertEquals("12345", wrappedW.toString());
+        }
+    }
+
+    @Test
+    public void testLimitExceededWithChar() throws IOException {
+        w.write('1');
+        w.write('2');
+        w.write('3');
+        w.write('4');
+        w.write('5');
+        try {
+            w.write('6');
+            fail();
+        } catch (LengthLimitExceededException e) {
+            assertEquals("12345", wrappedW.toString());
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/test/java/com/kenshoo/freemarker/view/FreeMarkerOnlineViewTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/com/kenshoo/freemarker/view/FreeMarkerOnlineViewTest.java b/src/test/java/com/kenshoo/freemarker/view/FreeMarkerOnlineViewTest.java
new file mode 100644
index 0000000..b73410f
--- /dev/null
+++ b/src/test/java/com/kenshoo/freemarker/view/FreeMarkerOnlineViewTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.view;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.junit.Test;
+
+import com.kenshoo.freemarker.services.AllowedSettingValuesMaps;
+
+import freemarker.core.HTMLOutputFormat;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: nir
+ * Date: 4/12/14
+ * Time: 11:13 PM
+ */
+public class FreeMarkerOnlineViewTest {
+
+    private static final String TEMPLATE = "Template";
+    private static final String DATA_MODEL = "DataModel";
+
+    @Test
+    public void testVieEmptyConstrucotr() {
+        FreeMarkerOnlineView view = new FreeMarkerOnlineView();
+        assertEquals(view.getTemplate(), "");
+        assertEquals(view.getDataModel(), "");
+        assertEquals(view.getOutputFormat(), AllowedSettingValuesMaps.DEFAULT_OUTPUT_FORMAT_KEY);
+        assertEquals(view.getLocale(), AllowedSettingValuesMaps.DEFAULT_LOCALE_KEY);
+        assertEquals(view.getTimeZone(), AllowedSettingValuesMaps.DEFAULT_TIME_ZONE_KEY);
+    }
+
+    @Test
+    public void testViewWhenAllOK() {
+        FreeMarkerOnlineView view = new FreeMarkerOnlineView();
+        
+        view.setTemplate(TEMPLATE);
+        view.setDataModel(DATA_MODEL);
+        String outputFormatStr = HTMLOutputFormat.INSTANCE.getName();
+        view.setOutputFormat(outputFormatStr);
+        String localeStr = Locale.GERMAN.toString();
+        view.setLocale(localeStr);
+        String timeZoneStr = TimeZone.getTimeZone("GMT+01").getID();
+        view.setTimeZone(timeZoneStr);
+        
+        assertEquals(view.getTemplate(), TEMPLATE);
+        assertEquals(view.getDataModel(), DATA_MODEL);
+        assertEquals(view.getOutputFormat(), outputFormatStr);
+        assertEquals(view.getLocale(), localeStr);
+        assertEquals(view.getTimeZone(), timeZoneStr);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/test/resources/spring/test-context.xml
----------------------------------------------------------------------
diff --git a/src/test/resources/spring/test-context.xml b/src/test/resources/spring/test-context.xml
new file mode 100644
index 0000000..2ca10e6
--- /dev/null
+++ b/src/test/resources/spring/test-context.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
+
+</beans>
\ No newline at end of file



[3/4] incubator-freemarker-online-tester git commit: initial commit

Posted by dd...@apache.org.
initial commit


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/commit/abb26297
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/tree/abb26297
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/diff/abb26297

Branch: refs/heads/master
Commit: abb26297cab30cafcaf57b7d1d4a5131d782f503
Parents: 0fb645b
Author: nirfeldman <ni...@kenshoo.com>
Authored: Mon Mar 20 18:37:10 2017 +0200
Committer: nirfeldman <ni...@kenshoo.com>
Committed: Mon Mar 20 18:37:10 2017 +0200

----------------------------------------------------------------------
 README.md                                       |  23 +
 build.gradle                                    | 130 ++++
 dependencies.gradle                             |  47 ++
 gradle/wrapper/gradle-wrapper.jar               | Bin 0 -> 49875 bytes
 gradle/wrapper/gradle-wrapper.properties        |   6 +
 gradlew                                         | 164 +++++
 gradlew.bat                                     |  90 +++
 intellij.gradle                                 |  37 ++
 settings.gradle                                 |   1 +
 .../dropwizard/ApplicationStartup.java          |  43 ++
 .../healthchecks/MyProjectHealthCheck.java      |  38 ++
 .../com/kenshoo/freemarker/model/ErrorCode.java |  24 +
 .../kenshoo/freemarker/model/ErrorResponse.java |  29 +
 .../freemarker/model/ExecuteRequest.java        |  77 +++
 .../freemarker/model/ExecuteResourceField.java  |  56 ++
 .../model/ExecuteResourceProblem.java           |  50 ++
 .../freemarker/model/ExecuteResponse.java       |  62 ++
 .../freemarker/model/SelectionOption.java       |  85 +++
 .../FreeMarkerOnlineExecuteResource.java        | 219 +++++++
 .../resources/FreeMarkerOnlineResource.java     |  65 ++
 .../services/AllowedSettingValuesMaps.java      | 112 ++++
 .../freemarker/services/FreeMarkerService.java  | 366 +++++++++++
 .../services/FreeMarkerServiceException.java    |  34 +
 .../services/FreeMarkerServiceResponse.java     |  70 +++
 .../freemarker/util/DataModelParser.java        | 264 ++++++++
 .../util/DataModelParsingException.java         |  35 ++
 .../kenshoo/freemarker/util/ExceptionUtils.java |  49 ++
 .../util/LengthLimitExceededException.java      |  31 +
 .../freemarker/util/LengthLimitedWriter.java    |  83 +++
 .../freemarker/view/FreeMarkerOnlineView.java   | 156 +++++
 .../core/FreeMarkerInternalsAccessor.java       |  61 ++
 src/main/resources/assets/css/main.css          | 114 ++++
 src/main/resources/assets/js/autosize.min.js    |   6 +
 .../resources/assets/js/jquery.autosize.min.js  |   6 +
 src/main/resources/assets/js/jquery.blockUI.js  | 620 +++++++++++++++++++
 src/main/resources/assets/js/script.js          |  97 +++
 src/main/resources/banner.txt                   |  10 +
 src/main/resources/freemarker-online.yml        |  22 +
 src/main/resources/spring/bootstrap-context.xml |  19 +
 src/main/resources/view/freemarker-online.ftl   | 129 ++++
 src/main/resources/view/utils.ftl               |  13 +
 .../platform/DropWizardServiceTest.java         |  41 ++
 .../platform/YamlPropertiesPersister.java       |  93 +++
 .../FreeMarkerOnlineExecuteResourceTest.java    | 159 +++++
 .../resources/FreeMarkerOnlineResourceTest.java |  72 +++
 .../FreeMarkerServiceResponseBuilderTest.java   |  63 ++
 .../services/FreeMarkerServiceTest.java         | 310 ++++++++++
 .../freemarker/util/DataModelParserTest.java    | 277 +++++++++
 .../util/LengthLimitedWriterTest.java           |  73 +++
 .../view/FreeMarkerOnlineViewTest.java          |  70 +++
 src/test/resources/spring/test-context.xml      |   7 +
 51 files changed, 4708 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c33aea5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+freemarker-online
+====================
+
+freemarker-online is a tool for any freemarker users to evalaute their freemarker expressions with an input.
+You can use it for enabling non developers to develop freemarker templates and evaluate it's values without the need for a developement enviornment.
+For a deployed version of this tool you can visit http://freemarker-online.kenshoo.com/
+
+Development Instuctions
+------------------------
+* Clone the repository to a local directory
+* Currently the project has an old dependency, "com.berico:fallwizard:1.1.1", which can't be found in any public repositories I know of. Thus, you have to Maven-install it locally:
+  1. Clone https://github.com/Berico-Technologies/Fallwizard.git
+  2. Check out this old version: 7ed7803496
+  3. `mvn install` it
+* Run "./gradlew build" from the cloned directory (use JDK 7, not 8!)
+* If you want to run it using IDEA run "./gradlew cleanidea idea" - this will generate the IDEA project for you.
+* For running the software from a command line, build `fatJar` (not `jar`) and then just hit "java -jar build/libs/freemarker-online-0.1.undef.jar server  src/main/resources/freemarker-online.yml"
+
+
+License
+-------
+
+FreeMarker-Online is licensed under the Apache License, Version 2.0. See LICENSE.txt for details.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/build.gradle
----------------------------------------------------------------------
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..890f3c2
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,130 @@
+group = 'com.kenshoo.freemarker'
+project.projectName = "freemarker-online"
+def BUILD_NUMBER = project.hasProperty('BUILD_NUMBER') ? "$BUILD_NUMBER" : 'undef'
+project.version = "0.1.$BUILD_NUMBER"
+project.yml = "$rootDir/src/main/resources/${projectName}.yml"
+project.jarName = "$rootDir/build/libs/${projectName}-${project.version}.jar"
+
+apply from: "intellij.gradle"
+apply from: "${rootDir}/dependencies.gradle"
+apply plugin: 'maven'
+apply plugin: 'java'
+apply plugin: 'jacoco'
+apply plugin: 'project-report'
+apply plugin: 'fatjar'
+apply plugin: 'fpm-packaging'
+
+// Because Spring 3 doesn't officially support Java 8, and indeed asm fails with it:
+// Must be after `apply plugin: 'java'`!
+project.sourceCompatibility = "1.7"
+project.targetCompatibility = "1.7"
+
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'eu.appsatori:gradle-fatjar-plugin:0.2-rc1'
+        classpath 'com.kenshoo:gradle-fpm:+'
+    }
+}
+
+repositories {
+    mavenCentral()
+    
+    // For com.berico:fallwizard:1.1.1 that must be locally installed; see README.md!
+    mavenLocal()
+}
+
+dependencies {
+    compile libraries.dropwizard
+    compile libraries.dropwizrd_views
+    compile libraries.springCore
+    compile libraries.springContext
+    compile libraries.springContextSupport
+    compile libraries.springExpression
+    compile libraries.springSecurityCore
+    compile libraries.springTransaction
+    compile libraries.springBeans
+    compile libraries.fallwizard
+
+    testCompile libraries.dropwizard_testing
+    testCompile libraries.springTest
+    testCompile libraries.junit
+    testCompile libraries.mockito
+    testCompile libraries.hamcrest
+
+    compile libraries.freemarker
+    compile libraries.jackson_databind
+    compile libraries.commonLangs
+    compile libraries.findBugs
+
+    testCompile libraries.selenium_java
+    testCompile libraries.jersey_grrizle
+    testCompile libraries.jersey_client
+    testCompile libraries.springJersey
+}
+
+compileJava {
+    options.compilerArgs << "-Werror"
+}
+compileTestJava {
+    options.compilerArgs << "-Werror"
+}
+
+task wrapper(type: Wrapper) {
+    gradleVersion = '1.10'
+}
+
+fatJar {
+    exclude "META-INF/*.SF"
+    exclude "META-INF/*.DSA"
+    exclude "META-INF/*.RSA"
+    manifest {
+        attributes 'Main-Class': 'com.kenshoo.freemarker.dropwizard.ApplicationStartup'
+        attributes 'Implementation-Version': "$version"
+    }
+}
+
+// this merges the spring files into META-INF properly
+fatJarPrepareFiles {
+    include 'META-INF/spring.handlers'
+    include 'META-INF/spring.schemas'
+}
+
+// wrap jar, upstart and yml into deb package:
+
+def stagingDir = new File(project.buildDir, "staging")
+
+task stageArtifacts(type: Copy) {
+    stagingDir.mkdir()
+    // place yml file and jar under staging dir
+    into new File(stagingDir, "/opt/${projectName}")
+    from "build/resources/main/${projectName}.yml"
+    from 'build/libs'
+}
+
+task stageStartScript << {
+    new File(stagingDir, "opt/${projectName}/start").withWriter("UTF-8") {
+        it.println "#!/usr/bin/env sh\n" +
+                " set -e\n" +
+                " exec java \$* -jar ${project.name}-${project.version}.jar server ${project.name}.yml"
+    }
+}
+
+task stageFiles(dependsOn: [stageArtifacts, stageStartScript]) << {
+    println "staging debian files"
+}
+
+// configure plugin to package the staging dir we've
+// just created, and to declare java-7 dependency
+packaging {
+    dependencies = ['openjdk-7-jre']
+    baseDir = stagingDir
+}
+
+
+fatJar.dependsOn jar
+build.dependsOn fatJar
+stageFiles.dependsOn build
+debian.dependsOn stageFiles

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/dependencies.gradle
----------------------------------------------------------------------
diff --git a/dependencies.gradle b/dependencies.gradle
new file mode 100644
index 0000000..fc4bc9c
--- /dev/null
+++ b/dependencies.gradle
@@ -0,0 +1,47 @@
+project.ext.set("libraries", "")
+ext.dw_version = "0.6.2";
+ext.spring_version = "3.2.2.RELEASE";
+ext.jackson_version = "2.5.1";
+
+project.libraries = [
+        dropwizard: "com.yammer.dropwizard:dropwizard-core:$dw_version",
+        dropwizard_client: "com.yammer.dropwizard:dropwizard-client:$dw_version",
+        dropwizrd_views: "com.yammer.dropwizard:dropwizard-views:$dw_version",
+        guava: 'com.google.guava:guava:13.0.1',
+        jersey_core: 'com.sun.jersey:jersey-core:1.1.4.1',
+
+        //Spring
+        springCore: "org.springframework:spring-core:$spring_version",
+        springContext: "org.springframework:spring-context:$spring_version",
+        springContextSupport: "org.springframework:spring-context-support:$spring_version",
+        springExpression: "org.springframework:spring-expression:$spring_version",
+        springTransaction: "org.springframework:spring-tx:$spring_version",
+        springBeans: "org.springframework:spring-beans:$spring_version",
+        springJersey: "com.sun.jersey.contribs:jersey-spring:1.7",
+
+        // Security
+        // Spring and Spring Security support for dropwizard
+        fallwizard: "com.berico:fallwizard:1.1.1",
+        // adheres to fallwizard version of spring security
+        springSecurityCore: "org.springframework.security:spring-security-core:3.1.4.RELEASE",
+        dropwizardAuth: "com.yammer.dropwizard:dropwizard-auth:$dw_version",
+
+        // Spring test
+        springTest: "org.springframework:spring-test:$spring_version",
+
+        //Test libs
+        dropwizard_testing: "com.yammer.dropwizard:dropwizard-testing:$dw_version",
+        junit: 'junit:junit-dep:4.11',
+        mockito: 'org.mockito:mockito-core:1.9.0',
+        hamcrest: 'org.hamcrest:hamcrest-library:1.3',
+        selenium_java: 'org.seleniumhq.selenium:selenium-java:2.41.0',
+        jersey_client: 'com.sun.jersey:jersey-client:1.17.1',
+        jersey_grrizle: 'com.sun.jersey.jersey-test-framework:jersey-test-framework-grizzly:1.17',
+
+        // Others
+        freemarker: 'org.freemarker:freemarker:2.3.24-incubating',
+        jackson_databind: "com.fasterxml.jackson.core:jackson-databind:$jackson_version",
+        commonLangs: "org.apache.commons:commons-lang3:3.3.2",
+        findBugs: "com.google.code.findbugs:annotations:3.0.0"
+
+]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/gradle/wrapper/gradle-wrapper.jar
----------------------------------------------------------------------
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..a7634b0
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/gradle/wrapper/gradle-wrapper.properties
----------------------------------------------------------------------
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..72f43b6
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Sep 10 11:20:58 IST 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.7-bin.zip

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/gradlew
----------------------------------------------------------------------
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/gradlew.bat
----------------------------------------------------------------------
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/intellij.gradle
----------------------------------------------------------------------
diff --git a/intellij.gradle b/intellij.gradle
new file mode 100644
index 0000000..4486da0
--- /dev/null
+++ b/intellij.gradle
@@ -0,0 +1,37 @@
+apply plugin: 'idea'
+
+idea {
+    project {
+        jdkName = '1.7'
+    }
+}
+
+idea.project.ipr {
+    withXml { provider ->
+        provider.node.component.find { it.@name == 'VcsDirectoryMappings' }.mapping.@vcs = 'Git'
+    }
+}
+
+idea.workspace.iws.withXml { provider ->
+    def runManager = provider.node.component.find { it.@name == 'RunManager' }
+    runManager.attributes().put('selected', "Application.MyProjectStartup")
+    Node list = runManager.list[0]
+    list.attributes().put('size', '1')
+    list.appendNode('item', [index: '0', class: 'java.lang.String', itemvalue: 'MyProjectStartup'])
+    def Application = runManager.appendNode('configuration', [default: 'false', name: "${projectName}", type: 'Application', factoryName: 'Application'])
+    Application.appendNode('extension', [name: 'coverage', enabled: 'false', merge: 'false', runner: 'idea'])
+    Application.appendNode('option', [name: 'MAIN_CLASS_NAME', value: "com.kenshoo.freemarker.dropwizard.ApplicationStartup"])
+    Application.appendNode('option', [name: 'VM_PARAMETERS', value: ""])
+    Application.appendNode('option', [name: 'PROGRAM_PARAMETERS', value: "server  src/main/resources/${projectName}.yml"])
+    Application.appendNode('option', [name: 'WORKING_DIRECTORY', value: "$projectDir"])
+    Application.appendNode('option', [name: 'ALTERNATIVE_JRE_PATH_ENABLED', value: "false"])
+    Application.appendNode('option', [name: 'ALTERNATIVE_JRE_PATH', value: ""])
+    Application.appendNode('option', [name: 'ENABLE_SWING_INSPECTOR', value: "false"])
+    Application.appendNode('option', [name: 'ENV_VARIABLES'])
+    Application.appendNode('option', [name: 'PASS_PARENT_ENVS', value: "true"])
+    Application.appendNode('module', [name: "${projectName}"])
+    Application.appendNode('envs')
+    Application.appendNode('RunnerSettings', [RunnerId: 'Run'])
+    Application.appendNode('ConfigurationWrapper', [RunnerId: 'Run'])
+    Application.appendNode('method')
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/settings.gradle
----------------------------------------------------------------------
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..854e7d2
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'freemarker-online'

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/dropwizard/ApplicationStartup.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/dropwizard/ApplicationStartup.java b/src/main/java/com/kenshoo/freemarker/dropwizard/ApplicationStartup.java
new file mode 100644
index 0000000..1808ce2
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/dropwizard/ApplicationStartup.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.dropwizard;
+
+import com.berico.fallwizard.SpringConfiguration;
+import com.berico.fallwizard.SpringService;
+import com.yammer.dropwizard.assets.AssetsBundle;
+import com.yammer.dropwizard.config.Bootstrap;
+import com.yammer.dropwizard.views.ViewBundle;
+
+/**
+ * User: dekely
+ * Date: 3/17/13
+ * Time: 10:39 AM
+ */
+public class ApplicationStartup extends SpringService<SpringConfiguration> {
+
+    public static void main(String[] args) throws Exception {
+        new ApplicationStartup().run(args);
+    }
+
+    @Override
+    public void initialize(Bootstrap<SpringConfiguration> bootstrap) {
+        bootstrap.setName("freemarker-online");
+        bootstrap.addBundle(new ViewBundle());
+        bootstrap.addBundle(new AssetsBundle("/assets/css", "/css"));
+        bootstrap.addBundle(new AssetsBundle("/assets/js", "/js"));
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/healthchecks/MyProjectHealthCheck.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/healthchecks/MyProjectHealthCheck.java b/src/main/java/com/kenshoo/freemarker/healthchecks/MyProjectHealthCheck.java
new file mode 100644
index 0000000..d5b35ba
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/healthchecks/MyProjectHealthCheck.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.healthchecks;
+
+import com.yammer.metrics.core.HealthCheck;
+import org.springframework.stereotype.Component;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: tzachz
+ * Date: 5/23/13
+ */
+@Component
+public class MyProjectHealthCheck extends HealthCheck {
+
+    // note that this is due to the default spring CTR
+    public MyProjectHealthCheck() {
+        super("MyProjectHealthCheck");
+    }
+
+    @Override
+    protected Result check() throws Exception {
+        return Result.healthy(); // we're always healthy!
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/model/ErrorCode.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/model/ErrorCode.java b/src/main/java/com/kenshoo/freemarker/model/ErrorCode.java
new file mode 100644
index 0000000..1e7f335
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/model/ErrorCode.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.model;
+
+/**
+ * Created by Pradeep on 8/30/2015.
+ */
+public enum ErrorCode {
+    FREEMARKER_SERVICE_TIMEOUT
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/model/ErrorResponse.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/model/ErrorResponse.java b/src/main/java/com/kenshoo/freemarker/model/ErrorResponse.java
new file mode 100644
index 0000000..063779a
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/model/ErrorResponse.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.model;
+
+/**
+ * Created by Pmuruge on 8/30/2015.
+ */
+public class ErrorResponse {
+    private ErrorCode errorCode;
+    private String errorDescription;
+
+    public ErrorResponse(ErrorCode errorCode, String errorDescription) {
+        this.errorCode = errorCode;
+        this.errorDescription = errorDescription;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/model/ExecuteRequest.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/model/ExecuteRequest.java b/src/main/java/com/kenshoo/freemarker/model/ExecuteRequest.java
new file mode 100644
index 0000000..b3c2163
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/model/ExecuteRequest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.model;
+
+/**
+ * Created by Pmuruge on 8/28/2015.
+ */
+public class ExecuteRequest {
+    private String template;
+    private String dataModel;
+    private String outputFormat;
+    private String locale;
+    private String timeZone;
+
+    public ExecuteRequest() {
+    }
+
+    public ExecuteRequest(String template, String dataModel) {
+        this.template = template;
+        this.dataModel = dataModel;
+    }
+
+    public String getDataModel() {
+        return dataModel;
+    }
+
+    public void setDataModel(String dataModel) {
+        this.dataModel = dataModel;
+    }
+
+    public String getTemplate() {
+
+        return template;
+    }
+
+    public void setTemplate(String template) {
+        this.template = template;
+    }
+
+    public String getOutputFormat() {
+        return outputFormat;
+    }
+
+    public void setOutputFormat(String outputFormat) {
+        this.outputFormat = outputFormat;
+    }
+
+    public String getLocale() {
+        return locale;
+    }
+
+    public void setLocale(String locale) {
+        this.locale = locale;
+    }
+
+    public String getTimeZone() {
+        return timeZone;
+    }
+
+    public void setTimeZone(String timeZone) {
+        this.timeZone = timeZone;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/model/ExecuteResourceField.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/model/ExecuteResourceField.java b/src/main/java/com/kenshoo/freemarker/model/ExecuteResourceField.java
new file mode 100644
index 0000000..05701ae
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/model/ExecuteResourceField.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.model;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * Created by Pmuruge on 8/31/2015.
+ */
+public enum ExecuteResourceField {
+    DATA_MODEL("dataModel"),
+    TEMPLATE("template"),
+    OUTPUT_FORMAT("outputFormat"),
+    LOCALE("locale"),
+    TIME_ZONE("timeZone");
+    
+    private final String fieldName;
+    
+    private ExecuteResourceField(String filedName) {
+        this.fieldName = filedName;
+    }
+    
+    public String toString() {
+        return getFieldName();
+    }
+    
+    @JsonValue
+    public String getFieldName() {
+        return fieldName;
+    }
+
+    @JsonCreator
+    public static ExecuteResourceField fromEnumString(String val) {
+        for(ExecuteResourceField field : values()) {
+            if(field.getFieldName().equals(val)) {
+                return field;
+            }
+        }
+        throw new IllegalArgumentException("Invalid string value passed: " + val);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/model/ExecuteResourceProblem.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/model/ExecuteResourceProblem.java b/src/main/java/com/kenshoo/freemarker/model/ExecuteResourceProblem.java
new file mode 100644
index 0000000..4dab2f9
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/model/ExecuteResourceProblem.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.kenshoo.freemarker.model;
+
+public class ExecuteResourceProblem {
+    
+    private ExecuteResourceField field;
+    private String message;
+    
+    // Needed for JSON unmarshalling
+    public ExecuteResourceProblem() {
+        //
+    }
+    
+    public ExecuteResourceProblem(ExecuteResourceField field, String message) {
+        this.field = field;
+        this.message = message;
+    }
+
+    public ExecuteResourceField getField() {
+        return field;
+    }
+
+    public void setField(ExecuteResourceField field) {
+        this.field = field;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/model/ExecuteResponse.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/model/ExecuteResponse.java b/src/main/java/com/kenshoo/freemarker/model/ExecuteResponse.java
new file mode 100644
index 0000000..c366ea5
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/model/ExecuteResponse.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.model;
+
+import java.util.List;
+
+/**
+ * Created by Pmuruge on 8/29/2015.
+ */
+public class ExecuteResponse {
+    private String result;
+    private List<ExecuteResourceProblem> problems;
+    private boolean truncatedResult;
+
+    public ExecuteResponse(String result, List<ExecuteResourceProblem> problems, boolean truncatedResult) {
+        this.result = result;
+        this.problems = problems;
+        this.truncatedResult = truncatedResult;
+    }
+
+    public ExecuteResponse() {
+
+    }
+
+    public List<ExecuteResourceProblem> getProblems() {
+        return problems;
+    }
+
+    public void setProblems(List<ExecuteResourceProblem> problems) {
+        this.problems = problems;
+    }
+
+    public boolean isTruncatedResult() {
+        return truncatedResult;
+    }
+
+    public void setTruncatedResult(boolean truncatedResult) {
+        this.truncatedResult = truncatedResult;
+    }
+
+    public String getResult() {
+        return result;
+    }
+
+    public void setResult(String result) {
+        this.result = result;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/model/SelectionOption.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/model/SelectionOption.java b/src/main/java/com/kenshoo/freemarker/model/SelectionOption.java
new file mode 100644
index 0000000..f911a22
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/model/SelectionOption.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.kenshoo.freemarker.model;
+
+public class SelectionOption implements Comparable<SelectionOption> {
+    
+    private final String value;
+    private final String label;
+    
+    public String getValue() {
+        return value;
+    }
+    
+    public String getLabel() {
+        return label;
+    }
+    
+    public SelectionOption(String value, String label) {
+        this.value = value;
+        this.label = label;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((label == null) ? 0 : label.hashCode());
+        result = prime * result + ((value == null) ? 0 : value.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;
+        }
+        SelectionOption other = (SelectionOption) obj;
+        if (label == null) {
+            if (other.label != null) {
+                return false;
+            }
+        } else if (!label.equals(other.label)) {
+            return false;
+        }
+        if (value == null) {
+            if (other.value != null) {
+                return false;
+            }
+        } else if (!value.equals(other.value)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int compareTo(SelectionOption o) {
+        int r = label.compareTo(o.label);
+        if (r != 0) {
+            return r;
+        }
+        
+        return value.compareTo(o.value);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineExecuteResource.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineExecuteResource.java b/src/main/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineExecuteResource.java
new file mode 100644
index 0000000..413463f
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineExecuteResource.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.resources;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.concurrent.RejectedExecutionException;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.kenshoo.freemarker.model.ErrorCode;
+import com.kenshoo.freemarker.model.ErrorResponse;
+import com.kenshoo.freemarker.model.ExecuteRequest;
+import com.kenshoo.freemarker.model.ExecuteResourceField;
+import com.kenshoo.freemarker.model.ExecuteResourceProblem;
+import com.kenshoo.freemarker.model.ExecuteResponse;
+import com.kenshoo.freemarker.services.AllowedSettingValuesMaps;
+import com.kenshoo.freemarker.services.FreeMarkerService;
+import com.kenshoo.freemarker.services.FreeMarkerServiceResponse;
+import com.kenshoo.freemarker.util.DataModelParser;
+import com.kenshoo.freemarker.util.DataModelParsingException;
+import com.kenshoo.freemarker.util.ExceptionUtils;
+
+import freemarker.core.OutputFormat;
+
+/**
+ * Created by pradeep on 8/28/2015.
+ */
+@Path("/api/execute")
+@Component
+public class FreeMarkerOnlineExecuteResource {
+    private static final int MAX_TEMPLATE_INPUT_LENGTH = 10000;
+
+    private static final int MAX_DATA_MODEL_INPUT_LENGTH = 10000;
+
+    private static final String MAX_TEMPLATE_INPUT_LENGTH_EXCEEDED_ERROR_MESSAGE
+            = "The template length has exceeded the {0} character limit set for this service.";
+
+    private static final String MAX_DATA_MODEL_INPUT_LENGTH_EXCEEDED_ERROR_MESSAGE
+            = "The data model length has exceeded the {0} character limit set for this service.";
+
+    private static final String UNKNOWN_OUTPUT_FORMAT_ERROR_MESSAGE = "Unknown output format: {0}";
+    private static final String UNKNOWN_LOCALE_ERROR_MESSAGE = "Unknown locale: {0}";
+    private static final String UNKNOWN_TIME_ZONE_ERROR_MESSAGE = "Unknown time zone: {0}";
+
+    private static final String SERVICE_OVERBURDEN_ERROR_MESSAGE
+            = "Sorry, the service is overburden and couldn't handle your request now. Try again later.";
+
+    static final String DATA_MODEL_ERROR_MESSAGE_HEADING = "Failed to parse data model:";
+    static final String DATA_MODEL_ERROR_MESSAGE_FOOTER = "Note: This is NOT a FreeMarker error message. "
+            + "The data model syntax is specific to this online service.";
+
+    @Autowired
+    private FreeMarkerService freeMarkerService;
+
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response formResult(
+            ExecuteRequest req) {
+        ExecuteResponse resp = new ExecuteResponse();
+        
+        if (StringUtils.isBlank(req.getTemplate()) && StringUtils.isBlank(req.getDataModel())) {
+            return Response.status(400).entity("Empty Template & data").build();
+        }
+
+        List<ExecuteResourceProblem> problems = new ArrayList<ExecuteResourceProblem>();
+        
+        String template = getTemplate(req, problems);
+        Map<String, Object> dataModel = getDataModel(req, problems);
+        OutputFormat outputFormat = getOutputFormat(req, problems);
+        Locale locale = getLocale(req, problems);
+        TimeZone timeZone = getTimeZone(req, problems);
+        
+        if (!problems.isEmpty()) {
+            resp.setProblems(problems);
+            return buildFreeMarkerResponse(resp);
+        }
+        
+        FreeMarkerServiceResponse freeMarkerServiceResponse;
+        try {
+            freeMarkerServiceResponse = freeMarkerService.calculateTemplateOutput(
+                    template, dataModel,
+                    outputFormat, locale, timeZone);
+        } catch (RejectedExecutionException e) {
+            String error = SERVICE_OVERBURDEN_ERROR_MESSAGE;
+            return Response.serverError().entity(new ErrorResponse(ErrorCode.FREEMARKER_SERVICE_TIMEOUT, error)).build();
+        }
+        if (!freeMarkerServiceResponse.isSuccesful()){
+            Throwable failureReason = freeMarkerServiceResponse.getFailureReason();
+            String error = ExceptionUtils.getMessageWithCauses(failureReason);
+            problems.add(new ExecuteResourceProblem(ExecuteResourceField.TEMPLATE, error));
+            resp.setProblems(problems);
+            return buildFreeMarkerResponse(resp);
+        }
+
+        String result = freeMarkerServiceResponse.getTemplateOutput();
+        resp.setResult(result);
+        resp.setTruncatedResult(freeMarkerServiceResponse.isTemplateOutputTruncated());
+        return buildFreeMarkerResponse(resp);
+    }
+
+    private String getTemplate(ExecuteRequest req, List<ExecuteResourceProblem> problems) {
+        String template = req.getTemplate();
+        
+        if (template.length() > MAX_TEMPLATE_INPUT_LENGTH) {
+            String error = formatMessage(MAX_TEMPLATE_INPUT_LENGTH_EXCEEDED_ERROR_MESSAGE, MAX_TEMPLATE_INPUT_LENGTH);
+            problems.add(new ExecuteResourceProblem(ExecuteResourceField.TEMPLATE, error));
+            return null;
+        }
+        
+        return template;
+    }
+
+    private Map<String, Object> getDataModel(ExecuteRequest req, List<ExecuteResourceProblem> problems) {
+        String dataModel = req.getDataModel();
+        
+        if (dataModel.length() > MAX_DATA_MODEL_INPUT_LENGTH) {
+            String error = formatMessage(
+                    MAX_DATA_MODEL_INPUT_LENGTH_EXCEEDED_ERROR_MESSAGE, MAX_DATA_MODEL_INPUT_LENGTH);
+            problems.add(new ExecuteResourceProblem(ExecuteResourceField.DATA_MODEL, error));
+            return null;
+        }
+        
+        try {
+            return DataModelParser.parse(dataModel, freeMarkerService.getFreeMarkerTimeZone());
+        } catch (DataModelParsingException e) {
+            problems.add(new ExecuteResourceProblem(ExecuteResourceField.DATA_MODEL, decorateResultText(e.getMessage())));
+            return null;
+        }
+    }
+
+    private OutputFormat getOutputFormat(ExecuteRequest req, List<ExecuteResourceProblem> problems) {
+        String outputFormatStr = req.getOutputFormat();
+        
+        if (StringUtils.isBlank(outputFormatStr)) {
+            return AllowedSettingValuesMaps.DEFAULT_OUTPUT_FORMAT;
+        }
+    
+        OutputFormat outputFormat = AllowedSettingValuesMaps.OUTPUT_FORMAT_MAP.get(outputFormatStr);
+        if (outputFormat == null) {
+            problems.add(new ExecuteResourceProblem(
+                    ExecuteResourceField.OUTPUT_FORMAT,
+                    formatMessage(UNKNOWN_OUTPUT_FORMAT_ERROR_MESSAGE, outputFormatStr)));
+        }
+        return outputFormat;
+    }
+
+    private Locale getLocale(ExecuteRequest req, List<ExecuteResourceProblem> problems) {
+        String localeStr = req.getLocale();
+        
+        if (StringUtils.isBlank(localeStr)) {
+            return AllowedSettingValuesMaps.DEFAULT_LOCALE;
+        }
+        
+        Locale locale = AllowedSettingValuesMaps.LOCALE_MAP.get(localeStr);
+        if (locale == null) {
+            problems.add(new ExecuteResourceProblem(
+                    ExecuteResourceField.LOCALE,
+                    formatMessage(UNKNOWN_LOCALE_ERROR_MESSAGE, localeStr)));
+        }
+        return locale;
+    }
+
+    private TimeZone getTimeZone(ExecuteRequest req, List<ExecuteResourceProblem> problems) {
+        String timeZoneStr = req.getTimeZone();
+        
+        if (StringUtils.isBlank(timeZoneStr)) {
+            return AllowedSettingValuesMaps.DEFAULT_TIME_ZONE;
+        }
+        
+        TimeZone timeZone = AllowedSettingValuesMaps.TIME_ZONE_MAP.get(timeZoneStr);
+        if (timeZone == null) {
+            problems.add(new ExecuteResourceProblem(
+                    ExecuteResourceField.TIME_ZONE,
+                    formatMessage(UNKNOWN_TIME_ZONE_ERROR_MESSAGE, timeZoneStr)));
+        }
+        return timeZone;
+    }
+
+    private Response buildFreeMarkerResponse(ExecuteResponse executeResponse){
+        return Response.ok().entity(executeResponse).build();
+    }
+    
+    private String decorateResultText(String resultText) {
+        return DATA_MODEL_ERROR_MESSAGE_HEADING + "\n\n" + resultText + "\n\n" + DATA_MODEL_ERROR_MESSAGE_FOOTER;
+    }
+    
+    private String formatMessage(String key, Object... params) {
+        return new MessageFormat(key, Locale.US).format(params);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineResource.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineResource.java b/src/main/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineResource.java
new file mode 100644
index 0000000..f429981
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineResource.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.resources;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.springframework.stereotype.Component;
+
+import com.kenshoo.freemarker.view.FreeMarkerOnlineView;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: shlomis
+ * Date: 9/1/13
+ * Time: 4:35 PM
+ */
+@Path("/")
+@Component
+public class FreeMarkerOnlineResource {
+
+    @GET
+    @Produces(MediaType.TEXT_HTML)
+    public FreeMarkerOnlineView blankForm() {
+        return new FreeMarkerOnlineView();
+    }
+
+    @POST
+    @Produces(MediaType.TEXT_HTML)
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public FreeMarkerOnlineView formResult(
+            @FormParam("template") String template,
+            @FormParam("dataModel") String dataModel,
+            @FormParam("outputFormat") String outputFormat,
+            @FormParam("locale") String locale,
+            @FormParam("timeZone") String timeZone) {
+        FreeMarkerOnlineView view = new FreeMarkerOnlineView();
+        view.setTemplate(template);
+        view.setDataModel(dataModel);
+        view.setOutputFormat(outputFormat);
+        view.setLocale(locale);
+        view.setTimeZone(timeZone);
+        view.setExecute(true);
+        return view;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/services/AllowedSettingValuesMaps.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/services/AllowedSettingValuesMaps.java b/src/main/java/com/kenshoo/freemarker/services/AllowedSettingValuesMaps.java
new file mode 100644
index 0000000..962e7c9
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/services/AllowedSettingValuesMaps.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.kenshoo.freemarker.services;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import freemarker.core.HTMLOutputFormat;
+import freemarker.core.OutputFormat;
+import freemarker.core.PlainTextOutputFormat;
+import freemarker.core.RTFOutputFormat;
+import freemarker.core.UndefinedOutputFormat;
+import freemarker.core.XHTMLOutputFormat;
+import freemarker.core.XMLOutputFormat;
+
+/**
+ * Maps of the setting values the caller can chose from (these are the value shown in a dropdown on the UI).
+ */
+public class AllowedSettingValuesMaps {
+
+    public static final OutputFormat DEFAULT_OUTPUT_FORMAT = UndefinedOutputFormat.INSTANCE;
+    public static final String DEFAULT_OUTPUT_FORMAT_KEY = DEFAULT_OUTPUT_FORMAT.getName();
+    public static final Map<String, OutputFormat> OUTPUT_FORMAT_MAP;
+    static {
+        Map<String, OutputFormat> map = new HashMap<String, OutputFormat>();
+        
+        addOutputFormatToMap(map, UndefinedOutputFormat.INSTANCE);
+        addOutputFormatToMap(map, HTMLOutputFormat.INSTANCE);
+        addOutputFormatToMap(map, XMLOutputFormat.INSTANCE);
+        addOutputFormatToMap(map, XHTMLOutputFormat.INSTANCE);
+        addOutputFormatToMap(map, RTFOutputFormat.INSTANCE);
+        addOutputFormatToMap(map, PlainTextOutputFormat.INSTANCE);
+        
+        OUTPUT_FORMAT_MAP = Collections.unmodifiableMap(map);
+    }
+    
+    private static void addOutputFormatToMap(Map<String, OutputFormat> map, OutputFormat outputFormat) {
+        map.put(outputFormat.getName(), outputFormat);
+    }
+
+    public static final Locale DEFAULT_LOCALE = Locale.US;
+    public static final String DEFAULT_LOCALE_KEY = DEFAULT_LOCALE.toString();
+    public static final Map<String, Locale> LOCALE_MAP;
+    static {
+        List<Locale> availableLocales = new ArrayList<Locale>(Arrays.asList(Locale.getAvailableLocales()));
+        
+        for (Iterator<Locale> iterator = availableLocales.iterator(); iterator.hasNext();) {
+            Locale locale = iterator.next();
+            // Don't bloat the list with "variants"
+            if (!StringUtils.isBlank(locale.getVariant())) {
+                iterator.remove();
+            }
+        }
+        
+        if (!availableLocales.contains(DEFAULT_LOCALE)) {
+            availableLocales.add(DEFAULT_LOCALE);
+        }
+        
+        Map<String, Locale> map = new HashMap<String, Locale>();
+        for (Locale locale : availableLocales) {
+            map.put(locale.toString(), locale);
+        }
+        
+        LOCALE_MAP = Collections.unmodifiableMap(map);
+    }
+
+    public static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone("America/Los_Angeles");
+    
+    public static final String DEFAULT_TIME_ZONE_KEY;
+    
+    public static final Map<String, TimeZone> TIME_ZONE_MAP;
+    static {
+        String[] availableIDs = TimeZone.getAvailableIDs();
+        
+        DEFAULT_TIME_ZONE_KEY = AllowedSettingValuesMaps.DEFAULT_TIME_ZONE.getID();
+        if (!ArrayUtils.contains(availableIDs, DEFAULT_TIME_ZONE_KEY)) {
+            ArrayUtils.add(availableIDs, DEFAULT_TIME_ZONE_KEY);
+        }
+        
+        Map<String, TimeZone> map = new HashMap<String, TimeZone>();
+        for (String timeZoneId : availableIDs) {
+            map.put(timeZoneId, TimeZone.getTimeZone(timeZoneId));
+        }
+        
+        TIME_ZONE_MAP = Collections.unmodifiableMap(map);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/services/FreeMarkerService.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/services/FreeMarkerService.java b/src/main/java/com/kenshoo/freemarker/services/FreeMarkerService.java
new file mode 100644
index 0000000..e594651
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/services/FreeMarkerService.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.services;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.TimeZone;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import com.kenshoo.freemarker.util.LengthLimitExceededException;
+import com.kenshoo.freemarker.util.LengthLimitedWriter;
+
+import freemarker.core.FreeMarkerInternalsAccessor;
+import freemarker.core.OutputFormat;
+import freemarker.core.ParseException;
+import freemarker.core.TemplateClassResolver;
+import freemarker.core.TemplateConfiguration;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateExceptionHandler;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: nir
+ * Date: 4/12/14
+ * Time: 10:15 AM
+ */
+@Service
+public class FreeMarkerService {
+
+    private static final int DEFAULT_MAX_OUTPUT_LENGTH = 100000;
+    private static final int DEFAULT_MAX_THREADS = Math.max(2,
+            (int) Math.round(Runtime.getRuntime().availableProcessors() * 3.0 / 4));
+    /** Not implemented yet, will need 2.3.22, even then a _CoreAPI call. */
+    private static final long DEFAULT_MAX_TEMPLATE_EXECUTION_TIME = 2000;
+    private static final int MIN_DEFAULT_MAX_QUEUE_LENGTH = 2;
+    private static final int MAX_DEFAULT_MAX_QUEUE_LENGTH_MILLISECONDS = 30000;
+    private static final long THREAD_KEEP_ALIVE_TIME = 4 * 1000;
+    private static final long ABORTION_LOOP_TIME_LIMIT = 5000;
+    private static final long ABORTION_LOOP_INTERRUPTION_DISTANCE = 50;
+    
+    private static final String MAX_OUTPUT_LENGTH_EXCEEDED_TERMINATION = "\n----------\n"
+            + "Aborted template processing, as the output length has exceeded the {0} character limit set for "
+            + "this service.";
+    
+    private static final Logger logger = LoggerFactory.getLogger(FreeMarkerService.class);
+
+    private final Configuration freeMarkerConfig;
+    
+    private ExecutorService templateExecutor;
+    
+    private int maxOutputLength = DEFAULT_MAX_OUTPUT_LENGTH;
+    
+    private int maxThreads = DEFAULT_MAX_THREADS;
+    private Integer maxQueueLength;
+    private long maxTemplateExecutionTime = DEFAULT_MAX_TEMPLATE_EXECUTION_TIME;
+
+    public FreeMarkerService() {
+        freeMarkerConfig = new Configuration(Configuration.getVersion());
+        freeMarkerConfig.setNewBuiltinClassResolver(TemplateClassResolver.ALLOWS_NOTHING_RESOLVER);
+        freeMarkerConfig.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+        freeMarkerConfig.setLogTemplateExceptions(false);
+        freeMarkerConfig.setLocale(AllowedSettingValuesMaps.DEFAULT_LOCALE);
+        freeMarkerConfig.setTimeZone(AllowedSettingValuesMaps.DEFAULT_TIME_ZONE);
+        freeMarkerConfig.setOutputFormat(AllowedSettingValuesMaps.DEFAULT_OUTPUT_FORMAT);
+        freeMarkerConfig.setOutputEncoding("UTF-8");
+    }
+    
+    /**
+     * @param templateSourceCode
+     *            The FTL to execute; not {@code null}.
+     * @param dataModel
+     *            The FreeMarker data-model to execute the template with; maybe {@code null}.
+     * @param outputFormat
+     *            The output format to execute the template with; maybe {@code null}.
+     * @param locale
+     *            The locale to execute the template with; maybe {@code null}.
+     * @param timeZone
+     *            The time zone to execute the template with; maybe {@code null}.
+     * 
+     * @return The result of the template parsing and evaluation. The method won't throw exception if that fails due to
+     *         errors in the template provided, instead it indicates this fact in the response object. That's because
+     *         this is a service for trying out the template language, so such errors are part of the normal operation.
+     * 
+     * @throws RejectedExecutionException
+     *             If the service is overburden and thus doing the calculation was rejected.
+     * @throws FreeMarkerServiceException
+     *             If the calculation fails from a reason that's not a mistake in the template and doesn't fit the
+     *             meaning of {@link RejectedExecutionException} either.
+     */
+    public FreeMarkerServiceResponse calculateTemplateOutput(
+            String templateSourceCode, Object dataModel, OutputFormat outputFormat, Locale locale, TimeZone timeZone)
+            throws RejectedExecutionException {
+        Objects.requireNonNull(templateExecutor, "templateExecutor was null - was postConstruct ever called?");
+        
+        final CalculateTemplateOutput task = new CalculateTemplateOutput(
+                templateSourceCode, dataModel, outputFormat, locale, timeZone);
+        Future<FreeMarkerServiceResponse> future = templateExecutor.submit(task);
+        
+        synchronized (task) {
+            while (!task.isTemplateExecutionStarted() && !task.isTaskEnded() && !future.isDone()) {
+                try {
+                    task.wait(50); // Timeout is needed to periodically check future.isDone()
+                } catch (InterruptedException e) {
+                    throw new FreeMarkerServiceException("Template execution task was interrupted.", e);
+                }
+            }
+        }
+        
+        try {
+            return future.get(maxTemplateExecutionTime, TimeUnit.MILLISECONDS);
+        } catch (ExecutionException e) {
+            throw new FreeMarkerServiceException("Template execution task unexpectedly failed", e.getCause());
+        } catch (InterruptedException e) {
+            throw new FreeMarkerServiceException("Template execution task was interrupted.", e);
+        } catch (TimeoutException e) {
+            // Exactly one interruption should be enough, and it should abort template processing pretty much
+            // immediately. But to be on the safe side we will interrupt in a loop, with a timeout.
+            final long abortionLoopStartTime = System.currentTimeMillis();
+            long timeLeft = ABORTION_LOOP_TIME_LIMIT;
+            boolean templateExecutionEnded = false;
+            do {
+                synchronized (task) {
+                    Thread templateExecutorThread = task.getTemplateExecutorThread();
+                    if (templateExecutorThread == null) {
+                        templateExecutionEnded = true;
+                    } else {
+                        FreeMarkerInternalsAccessor.interruptTemplateProcessing(templateExecutorThread);
+                        logger.debug("Trying to interrupt overly long template processing (" + timeLeft + " ms left).");
+                    }
+                }
+                if (!templateExecutionEnded) {
+                    try {
+                        timeLeft = ABORTION_LOOP_TIME_LIMIT - (System.currentTimeMillis() - abortionLoopStartTime);
+                        if (timeLeft > 0) {
+                            Thread.sleep(ABORTION_LOOP_INTERRUPTION_DISTANCE);
+                        }
+                    } catch (InterruptedException eInt) {
+                        logger.error("Template execution abortion loop was interrupted", eInt);
+                        timeLeft = 0;
+                    }
+                }
+            } while (!templateExecutionEnded && timeLeft > 0);
+            
+            if (templateExecutionEnded) {
+                logger.debug("Long template processing has ended.");
+                try {
+                    return future.get();
+                } catch (InterruptedException | ExecutionException e1) {
+                    throw new FreeMarkerServiceException("Failed to get result from template executor task", e);
+                }
+            } else {
+                throw new FreeMarkerServiceException(
+                        "Couldn't stop long running template processing within " + ABORTION_LOOP_TIME_LIMIT
+                        + " ms. It's possibly stuck forever. Such problems can exhaust the executor pool. "
+                        + "Template (quoted): " + StringEscapeUtils.escapeJava(templateSourceCode));
+            }
+        }
+    }
+    
+    public int getMaxOutputLength() {
+        return maxOutputLength;
+    }
+
+    public void setMaxOutputLength(int maxOutputLength) {
+        this.maxOutputLength = maxOutputLength;
+    }
+
+    public int getMaxThreads() {
+        return maxThreads;
+    }
+    
+    public void setMaxThreads(int maxThreads) {
+        this.maxThreads = maxThreads;
+    }
+    
+    public int getMaxQueueLength() {
+        return maxQueueLength;
+    }
+    
+    public void setMaxQueueLength(int maxQueueLength) {
+        this.maxQueueLength = maxQueueLength;
+    }
+
+    public long getMaxTemplateExecutionTime() {
+        return maxTemplateExecutionTime;
+    }
+    
+    public void setMaxTemplateExecutionTime(long maxTemplateExecutionTime) {
+        this.maxTemplateExecutionTime = maxTemplateExecutionTime;
+    }
+
+    /**
+     * Returns the time zone used by the FreeMarker templates.
+     */
+    public TimeZone getFreeMarkerTimeZone() {
+        return freeMarkerConfig.getTimeZone();
+    }
+    
+    private FreeMarkerServiceResponse createFailureResponse(Throwable e) {
+        logger.debug("The template had error(s)", e);
+        return new FreeMarkerServiceResponse.Builder().buildForFailure(e);
+    }
+
+    @PostConstruct
+    public void postConstruct() {
+        int actualMaxQueueLength = maxQueueLength != null
+                ? maxQueueLength
+                : Math.max(
+                        MIN_DEFAULT_MAX_QUEUE_LENGTH,
+                        (int) (MAX_DEFAULT_MAX_QUEUE_LENGTH_MILLISECONDS / maxTemplateExecutionTime));
+        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
+                maxThreads, maxThreads,
+                THREAD_KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS,
+                new BlockingArrayQueue<Runnable>(actualMaxQueueLength));
+        threadPoolExecutor.allowCoreThreadTimeOut(true);
+        templateExecutor = threadPoolExecutor;
+    }
+    
+    private class CalculateTemplateOutput implements Callable<FreeMarkerServiceResponse> {
+        
+        private boolean templateExecutionStarted;
+        private Thread templateExecutorThread;
+        private final String templateSourceCode;
+        private final Object dataModel;
+        private final OutputFormat outputFormat;
+        private final Locale locale;
+        private final TimeZone timeZone;
+        private boolean taskEnded;
+
+        private CalculateTemplateOutput(String templateSourceCode, Object dataModel,
+                OutputFormat outputFormat, Locale locale, TimeZone timeZone) {
+            this.templateSourceCode = templateSourceCode;
+            this.dataModel = dataModel;
+            this.outputFormat = outputFormat;
+            this.locale = locale;
+            this.timeZone = timeZone;
+        }
+        
+        @Override
+        public FreeMarkerServiceResponse call() throws Exception {
+            try {
+                Template template;
+                try {
+                    TemplateConfiguration tCfg = new TemplateConfiguration();
+                    tCfg.setParentConfiguration(freeMarkerConfig);
+                    if (outputFormat != null) {
+                        tCfg.setOutputFormat(outputFormat);
+                    }
+                    if (locale != null) {
+                        tCfg.setLocale(locale);
+                    }
+                    if (timeZone != null) {
+                        tCfg.setTimeZone(timeZone);
+                    }
+                    
+                    template = new Template(null, null,
+                            new StringReader(templateSourceCode), freeMarkerConfig, tCfg, null);
+                    
+                    tCfg.apply(template);
+                } catch (ParseException e) {
+                    // Expected (part of normal operation)
+                    return createFailureResponse(e);
+                } catch (Exception e) {
+                    // Not expected
+                    throw new FreeMarkerServiceException("Unexpected exception during template parsing", e);
+                }
+                
+                FreeMarkerInternalsAccessor.makeTemplateInterruptable(template);
+                
+                boolean resultTruncated;
+                StringWriter writer = new StringWriter();
+                try {
+                    synchronized (this) {
+                        templateExecutorThread = Thread.currentThread(); 
+                        templateExecutionStarted = true;
+                        notifyAll();
+                    }
+                    try {
+                        template.process(dataModel, new LengthLimitedWriter(writer, maxOutputLength));
+                    } finally {
+                        synchronized (this) {
+                            templateExecutorThread = null;
+                            FreeMarkerInternalsAccessor.clearAnyPendingTemplateProcessingInterruption();
+                        }
+                    }
+                    resultTruncated = false;
+                } catch (LengthLimitExceededException e) {
+                    // Not really an error, we just cut the output here.
+                    resultTruncated = true;
+                    writer.write(new MessageFormat(MAX_OUTPUT_LENGTH_EXCEEDED_TERMINATION, AllowedSettingValuesMaps.DEFAULT_LOCALE)
+                            .format(new Object[] { maxOutputLength }));
+                    // Falls through
+                } catch (TemplateException e) {
+                    // Expected (part of normal operation)
+                    return createFailureResponse(e);
+                } catch (Exception e) {
+                    if (FreeMarkerInternalsAccessor.isTemplateProcessingInterruptedException(e)) {
+                        return new FreeMarkerServiceResponse.Builder().buildForFailure(new TimeoutException(
+                                "Template processing was aborted for exceeding the " + getMaxTemplateExecutionTime()
+                                + " ms time limit set for this online service. This is usually because you have "
+                                + "a very long running #list (or other kind of loop) in your template.")); 
+                    }
+                    // Not expected
+                    throw new FreeMarkerServiceException("Unexpected exception during template evaluation", e);
+                }
+                
+                return new FreeMarkerServiceResponse.Builder().buildForSuccess(writer.toString(), resultTruncated);
+            } finally {
+                synchronized (this) {
+                    taskEnded = true;
+                    notifyAll();
+                }
+            }
+        }
+        
+        private synchronized boolean isTemplateExecutionStarted() {
+            return templateExecutionStarted;
+        }
+
+        private synchronized boolean isTaskEnded() {
+            return taskEnded;
+        }
+        
+        /**
+         * @return non-{@code null} after the task execution has actually started, but before it has finished.
+         */
+        private synchronized Thread getTemplateExecutorThread() {
+            return templateExecutorThread;
+        }
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/services/FreeMarkerServiceException.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/services/FreeMarkerServiceException.java b/src/main/java/com/kenshoo/freemarker/services/FreeMarkerServiceException.java
new file mode 100644
index 0000000..118528d
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/services/FreeMarkerServiceException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.services;
+
+/**
+ * When {@link FreeMarkerService} fails on an unexpected way (non-user error). 
+ */
+public class FreeMarkerServiceException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    public FreeMarkerServiceException(String message) {
+        super(message);
+        // TODO Auto-generated constructor stub
+    }
+
+    public FreeMarkerServiceException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/services/FreeMarkerServiceResponse.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/services/FreeMarkerServiceResponse.java b/src/main/java/com/kenshoo/freemarker/services/FreeMarkerServiceResponse.java
new file mode 100644
index 0000000..eab7ce3
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/services/FreeMarkerServiceResponse.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.services;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: nir
+ * Date: 4/12/14
+ * Time: 11:28 AM
+ */
+public class FreeMarkerServiceResponse {
+    
+    private final String templateOutput;
+    private final boolean templateOutputTruncated;
+    private final Throwable failureReason;
+
+    FreeMarkerServiceResponse(String templateOutput, boolean templateOutputTruncated) {
+        this.templateOutput = templateOutput;
+        this.templateOutputTruncated = templateOutputTruncated;
+        this.failureReason = null;
+    }
+
+    FreeMarkerServiceResponse(Throwable failureReason) {
+        this.templateOutput = null;
+        this.templateOutputTruncated = false;
+        this.failureReason = failureReason;
+    }
+    
+    public String getTemplateOutput() {
+        return templateOutput;
+    }
+
+    public boolean isTemplateOutputTruncated() {
+        return templateOutputTruncated;
+    }
+
+    public boolean isSuccesful() {
+        return failureReason == null;
+    }
+
+    public Throwable getFailureReason() {
+        return failureReason;
+    }
+
+    public static class Builder {
+        
+        public FreeMarkerServiceResponse buildForSuccess(String result, boolean resultTruncated){
+            return new FreeMarkerServiceResponse(result, resultTruncated);
+        }
+
+        public FreeMarkerServiceResponse buildForFailure(Throwable failureReason){
+            return new FreeMarkerServiceResponse(failureReason);
+        }
+        
+    }
+    
+}


[4/4] incubator-freemarker-online-tester git commit: Contribution from Kenshoo

Posted by dd...@apache.org.
Contribution from Kenshoo

Merge commit 'refs/pull/2/head' of https://github.com/apache/incubator-freemarker-online-tester


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/commit/fc99d2b4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/tree/fc99d2b4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/diff/fc99d2b4

Branch: refs/heads/master
Commit: fc99d2b4fa6a9a863428ef737febc6413fcaa4da
Parents: 0fb645b abb2629
Author: ddekany <dd...@apache.org>
Authored: Sat Apr 1 21:43:31 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sat Apr 1 21:46:52 2017 +0200

----------------------------------------------------------------------
 README.md                                       |  23 +
 build.gradle                                    | 130 ++++
 dependencies.gradle                             |  47 ++
 gradle/wrapper/gradle-wrapper.jar               | Bin 0 -> 49875 bytes
 gradle/wrapper/gradle-wrapper.properties        |   6 +
 gradlew                                         | 164 +++++
 gradlew.bat                                     |  90 +++
 intellij.gradle                                 |  37 ++
 settings.gradle                                 |   1 +
 .../dropwizard/ApplicationStartup.java          |  43 ++
 .../healthchecks/MyProjectHealthCheck.java      |  38 ++
 .../com/kenshoo/freemarker/model/ErrorCode.java |  24 +
 .../kenshoo/freemarker/model/ErrorResponse.java |  29 +
 .../freemarker/model/ExecuteRequest.java        |  77 +++
 .../freemarker/model/ExecuteResourceField.java  |  56 ++
 .../model/ExecuteResourceProblem.java           |  50 ++
 .../freemarker/model/ExecuteResponse.java       |  62 ++
 .../freemarker/model/SelectionOption.java       |  85 +++
 .../FreeMarkerOnlineExecuteResource.java        | 219 +++++++
 .../resources/FreeMarkerOnlineResource.java     |  65 ++
 .../services/AllowedSettingValuesMaps.java      | 112 ++++
 .../freemarker/services/FreeMarkerService.java  | 366 +++++++++++
 .../services/FreeMarkerServiceException.java    |  34 +
 .../services/FreeMarkerServiceResponse.java     |  70 +++
 .../freemarker/util/DataModelParser.java        | 264 ++++++++
 .../util/DataModelParsingException.java         |  35 ++
 .../kenshoo/freemarker/util/ExceptionUtils.java |  49 ++
 .../util/LengthLimitExceededException.java      |  31 +
 .../freemarker/util/LengthLimitedWriter.java    |  83 +++
 .../freemarker/view/FreeMarkerOnlineView.java   | 156 +++++
 .../core/FreeMarkerInternalsAccessor.java       |  61 ++
 src/main/resources/assets/css/main.css          | 114 ++++
 src/main/resources/assets/js/autosize.min.js    |   6 +
 .../resources/assets/js/jquery.autosize.min.js  |   6 +
 src/main/resources/assets/js/jquery.blockUI.js  | 620 +++++++++++++++++++
 src/main/resources/assets/js/script.js          |  97 +++
 src/main/resources/banner.txt                   |  10 +
 src/main/resources/freemarker-online.yml        |  22 +
 src/main/resources/spring/bootstrap-context.xml |  19 +
 src/main/resources/view/freemarker-online.ftl   | 129 ++++
 src/main/resources/view/utils.ftl               |  13 +
 .../platform/DropWizardServiceTest.java         |  41 ++
 .../platform/YamlPropertiesPersister.java       |  93 +++
 .../FreeMarkerOnlineExecuteResourceTest.java    | 159 +++++
 .../resources/FreeMarkerOnlineResourceTest.java |  72 +++
 .../FreeMarkerServiceResponseBuilderTest.java   |  63 ++
 .../services/FreeMarkerServiceTest.java         | 310 ++++++++++
 .../freemarker/util/DataModelParserTest.java    | 277 +++++++++
 .../util/LengthLimitedWriterTest.java           |  73 +++
 .../view/FreeMarkerOnlineViewTest.java          |  70 +++
 src/test/resources/spring/test-context.xml      |   7 +
 51 files changed, 4708 insertions(+)
----------------------------------------------------------------------



[2/4] incubator-freemarker-online-tester git commit: initial commit

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/util/DataModelParser.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/util/DataModelParser.java b/src/main/java/com/kenshoo/freemarker/util/DataModelParser.java
new file mode 100644
index 0000000..28cf6ff
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/util/DataModelParser.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.util;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.math.BigDecimal;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.DocumentBuilder;
+
+import org.springframework.util.StringUtils;
+import org.w3c.dom.Document;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import freemarker.ext.dom.NodeModel;
+import freemarker.template.utility.DateUtil;
+import freemarker.template.utility.DateUtil.CalendarFieldsToDateConverter;
+import freemarker.template.utility.DateUtil.DateParseException;
+import freemarker.template.utility.DateUtil.TrivialCalendarFieldsToDateConverter;
+
+/**
+ * Parses the text that the user enters into the data model input field.
+ */
+public final class DataModelParser {
+
+    private static final String KEYWORD_NEGATIVE_INFINITY = "-Infinity";
+
+    private static final String KEYWORD_POSITIVE_INFINITY = "+Infinity";
+
+    private static final String KEYWORD_INFINITY = "Infinity";
+
+    private static final String KEYWORD_TRUE = "true";
+
+    private static final String KEYWORD_FALSE = "false";
+
+    private static final String KEYWORD_NULL = "null";
+
+    private static final String KEYWORD_NAN = "NaN";
+
+    /** Matches a line starting like "someVariable=". */
+    private static final Pattern ASSIGNMENT_START = Pattern.compile(
+            "^\\s*"
+            + "(\\p{L}[\\p{L}\\p{N}\\.:\\-_$@]*)" // name
+            + "[ \t]*=\\s*",
+            Pattern.MULTILINE);
+
+    /** Matches a value that starts like a number, or probably meant to be number at least. */
+    private static final Pattern NUMBER_LIKE = Pattern.compile("[+-]?[\\.,]?[0-9].*", Pattern.DOTALL);
+    
+    private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
+
+    private DataModelParser() {
+        // Not meant to be instantiated
+    }
+
+    public static Map<String, Object> parse(String src, TimeZone timeZone) throws DataModelParsingException {
+        if (!StringUtils.hasText(src)) {
+            return Collections.emptyMap();
+        }
+
+        Map<String, Object> dataModel = new LinkedHashMap<>();
+
+        String lastName = null;
+        int lastAssignmentStartEnd = 0;
+        final Matcher assignmentStart = ASSIGNMENT_START.matcher(src);
+        findAssignments: while (true) {
+            boolean hasNextAssignment = assignmentStart.find(lastAssignmentStartEnd);
+
+            if (lastName != null) {
+                String value = src.substring(
+                        lastAssignmentStartEnd, hasNextAssignment ? assignmentStart.start() : src.length())
+                        .trim();
+                final Object parsedValue;
+                try {
+                    parsedValue = parseValue(value, timeZone);
+                } catch (DataModelParsingException e) {
+                    throw new DataModelParsingException(
+                            "Failed to parse the value of \"" + lastName + "\":\n" + e.getMessage(), e.getCause());
+                }
+                dataModel.put(lastName, parsedValue);
+            }
+
+            if (lastName == null && (!hasNextAssignment || assignmentStart.start() != 0)) {
+                throw new DataModelParsingException(
+                        "The data model specification must start with an assignment (name=value).");
+            }
+
+            if (!hasNextAssignment) {
+                break findAssignments;
+            }
+
+            lastName = assignmentStart.group(1).trim();
+            lastAssignmentStartEnd = assignmentStart.end();
+        }
+
+        return dataModel;
+    }
+    
+    private static Object parseValue(String value, TimeZone timeZone) throws DataModelParsingException {
+        // Note: Because we fall back to interpret the input as a literal string value when it doesn't look like
+        // anything else (like a number, boolean, etc.), it's important to avoid misunderstandings, and throw exception
+        // in suspicious situations. The user can always quote the string value if we are "too smart". But he will
+        // be confused about the rules of FreeMarker if what he believes to be a non-string is misinterpreted by this
+        // parser as a string. Getting sometimes an error and then quoting the string is better than that.
+        
+        if (value.endsWith(";")) {  // Tolerate this habit of Java and JavaScript programmers
+            value = value.substring(value.length() - 1).trim();
+        }
+        
+        if (NUMBER_LIKE.matcher(value).matches()) {
+            try {
+                return new BigDecimal(value);
+            } catch (NumberFormatException e) {
+                // Maybe it's a ISO 8601 Date/time/datetime
+                CalendarFieldsToDateConverter calToDateConverter = new TrivialCalendarFieldsToDateConverter();
+                
+                DateParseException attemptedTemportalPExc = null;
+                String attemptedTemporalType = null;
+                final int dashIdx = value.indexOf('-');
+                final int colonIdx = value.indexOf(':');
+                if (value.indexOf('T') > 1 || (dashIdx > 1 && colonIdx > dashIdx)) {
+                    try {
+                        return new Timestamp(
+                                DateUtil.parseISO8601DateTime(value, timeZone, calToDateConverter).getTime());
+                    } catch (DateParseException pExc) {
+                        attemptedTemporalType = "date-time";
+                        attemptedTemportalPExc = pExc;
+                    }
+                } else if (dashIdx > 1) {
+                    try {
+                        return new java.sql.Date(
+                                DateUtil.parseISO8601Date(value, timeZone, calToDateConverter).getTime());
+                    } catch (DateParseException pExc) {
+                        attemptedTemporalType = "date";
+                        attemptedTemportalPExc = pExc;
+                    }
+                } else if (colonIdx > 1) { 
+                    try {
+                        return new Time(
+                                DateUtil.parseISO8601Time(value, timeZone, calToDateConverter).getTime());
+                    } catch (DateParseException pExc) {
+                        attemptedTemporalType = "time";
+                        attemptedTemportalPExc = pExc;
+                    }
+                }
+                if (attemptedTemportalPExc == null) {
+                    throw new DataModelParsingException("Malformed number: " + value, e);
+                } else {
+                    throw new DataModelParsingException(
+                            "Malformed ISO 8601 " + attemptedTemporalType + " (or malformed number): " + 
+                            attemptedTemportalPExc.getMessage(), e.getCause());
+                }
+            }
+        } else if (value.startsWith("\"")) {
+            try {
+                return JSON_MAPPER.readValue(value, String.class);
+            } catch (IOException e) {
+                throw new DataModelParsingException(
+                        "Malformed quoted string (using JSON syntax): " + getMessageWithoutLocation(e),
+                        e);
+            }
+        } else if (value.startsWith("\'")) {
+            throw new DataModelParsingException(
+                    "Malformed quoted string (using JSON syntax): Use \" character for quotation, not \' character.");
+        } else if (value.startsWith("[")) {
+            try {
+                return JSON_MAPPER.readValue(value, List.class);
+            } catch (IOException e) {
+                throw new DataModelParsingException(
+                        "Malformed list (using JSON syntax): " + getMessageWithoutLocation(e),
+                        e);
+            }
+        } else if (value.startsWith("{")) {
+            try {
+                return JSON_MAPPER.readValue(value, LinkedHashMap.class);
+            } catch (IOException e) {
+                throw new DataModelParsingException(
+                        "Malformed list (using JSON syntax): " + getMessageWithoutLocation(e),
+                        e);
+            }
+        } else if (value.startsWith("<")) {
+            try {
+                DocumentBuilder builder = NodeModel.getDocumentBuilderFactory().newDocumentBuilder();
+                ErrorHandler errorHandler = NodeModel.getErrorHandler();
+                if (errorHandler != null) builder.setErrorHandler(errorHandler);
+                final Document doc = builder.parse(new InputSource(new StringReader(value)));
+                NodeModel.simplify(doc);
+                return doc;
+            } catch (SAXException e) {
+                final String saxMsg = e.getMessage();
+                throw new DataModelParsingException("Malformed XML: " + (saxMsg != null ? saxMsg : e), e);
+            } catch (Exception e) {
+                throw new DataModelParsingException("XML parsing has failed with internal error: " + e, e);
+            }
+        } else if (value.equalsIgnoreCase(KEYWORD_TRUE)) {
+            checkKeywordCase(value, KEYWORD_TRUE);
+            return Boolean.TRUE;
+        } else if (value.equalsIgnoreCase(KEYWORD_FALSE)) {
+            checkKeywordCase(value, KEYWORD_FALSE);
+            return Boolean.FALSE;
+        } else if (value.equalsIgnoreCase(KEYWORD_NULL)) {
+            checkKeywordCase(value, KEYWORD_NULL);
+            return null;
+        } else if (value.equalsIgnoreCase(KEYWORD_NAN)) {
+            checkKeywordCase(value, KEYWORD_NAN);
+            return Double.NaN;
+        } else if (value.equalsIgnoreCase(KEYWORD_INFINITY)) {
+            checkKeywordCase(value, KEYWORD_INFINITY);
+            return Double.POSITIVE_INFINITY;
+        } else if (value.equalsIgnoreCase(KEYWORD_POSITIVE_INFINITY)) {
+            checkKeywordCase(value, KEYWORD_POSITIVE_INFINITY);
+            return Double.POSITIVE_INFINITY;
+        } else if (value.equalsIgnoreCase(KEYWORD_NEGATIVE_INFINITY)) {
+            checkKeywordCase(value, KEYWORD_NEGATIVE_INFINITY);
+            return Double.NEGATIVE_INFINITY;
+        } else if (value.length() == 0) {
+            throw new DataModelParsingException(
+                    "Empty value. (If you indeed wanted a 0 length string, quote it, like \"\".)");
+        } else {
+            return value;
+        }
+    }
+
+    private static String getMessageWithoutLocation(IOException e) {
+        return e instanceof JsonProcessingException
+                ? ((JsonProcessingException) e).getOriginalMessage()
+                : e.getMessage();
+    }
+
+    private static void checkKeywordCase(String inputKeyword, String correctKeyword) throws DataModelParsingException {
+        if (!correctKeyword.equals(inputKeyword)) {
+            throw new DataModelParsingException("Keywords are case sensitive; the correct form is: "
+                    + correctKeyword);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/util/DataModelParsingException.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/util/DataModelParsingException.java b/src/main/java/com/kenshoo/freemarker/util/DataModelParsingException.java
new file mode 100644
index 0000000..cd24407
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/util/DataModelParsingException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.util;
+
+import java.util.TimeZone;
+
+/**
+ * Thrown by {@link DataModelParser#parse(String, TimeZone)}.
+ */
+public class DataModelParsingException extends Exception {
+
+    private static final long serialVersionUID = 1L;
+
+    public DataModelParsingException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public DataModelParsingException(String message) {
+        super(message);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/util/ExceptionUtils.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/util/ExceptionUtils.java b/src/main/java/com/kenshoo/freemarker/util/ExceptionUtils.java
new file mode 100644
index 0000000..999c450
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/util/ExceptionUtils.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.util;
+
+import freemarker.core.ParseException;
+import freemarker.template.TemplateException;
+
+public final class ExceptionUtils {
+
+    private ExceptionUtils() {
+        // Not meant to be instantiated
+    }
+
+    /**
+     * The error message (and sometimes also the class), and then the same with the cause exception, and so on. Doesn't
+     * contain the stack trace or other location information.
+     */
+    public static String getMessageWithCauses(final Throwable exc) {
+        StringBuilder sb = new StringBuilder();
+        
+        Throwable curExc = exc;
+        while (curExc != null) {
+            if (curExc != exc) {
+                sb.append("\n\nCaused by:\n");
+            }
+            String msg = curExc.getMessage();
+            if (msg == null || !(curExc instanceof TemplateException || curExc instanceof ParseException)) {
+                sb.append(curExc.getClass().getName()).append(": ");
+            }
+            sb.append(msg);
+            curExc = curExc.getCause();
+        }
+        return sb.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/util/LengthLimitExceededException.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/util/LengthLimitExceededException.java b/src/main/java/com/kenshoo/freemarker/util/LengthLimitExceededException.java
new file mode 100644
index 0000000..67effd0
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/util/LengthLimitExceededException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.util;
+
+import java.io.IOException;
+
+/**
+ * Thrown by {@link LengthLimitedWriter}.
+ */
+public class LengthLimitExceededException extends IOException {
+
+    private static final long serialVersionUID = 1L;
+    
+    public LengthLimitExceededException() {
+        super("The outout String length limit of the Writer was exceeded.");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/util/LengthLimitedWriter.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/util/LengthLimitedWriter.java b/src/main/java/com/kenshoo/freemarker/util/LengthLimitedWriter.java
new file mode 100644
index 0000000..a4d1450
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/util/LengthLimitedWriter.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.util;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ * A {@link StringWriter} that limits its buffer size, and throws {@link LengthLimitExceededException} when that's
+ * exceeded.
+ */
+public class LengthLimitedWriter extends FilterWriter {
+    
+    private int lengthLeft;
+
+    public LengthLimitedWriter(Writer writer, int lengthLimit) {
+        super(writer);
+        this.lengthLeft = lengthLimit;
+    }
+
+    @Override
+    public void write(int c) throws IOException {
+        if (lengthLeft < 1) {
+            throw new LengthLimitExceededException();
+        }
+        
+        super.write(c);
+        
+        lengthLeft--;
+    }
+
+    @Override
+    public void write(char[] cbuf, int off, int len) throws IOException {
+        final boolean lengthExceeded;
+        if (lengthLeft < len) {
+            len = lengthLeft;
+            lengthExceeded = true;
+        } else {
+            lengthExceeded = false;
+        }
+        
+        super.write(cbuf, off, len);
+        lengthLeft -= len;
+        
+        if (lengthExceeded) {
+            throw new LengthLimitExceededException();
+        }
+    }
+
+    @Override
+    public void write(String str, int off, int len) throws IOException {
+        final boolean lengthExceeded;
+        if (lengthLeft < len) {
+            len = lengthLeft;
+            lengthExceeded = true;
+        } else {
+            lengthExceeded = false;
+        }
+        
+        super.write(str, off, len);
+        lengthLeft -= len;
+        
+        if (lengthExceeded) {
+            throw new LengthLimitExceededException();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/com/kenshoo/freemarker/view/FreeMarkerOnlineView.java
----------------------------------------------------------------------
diff --git a/src/main/java/com/kenshoo/freemarker/view/FreeMarkerOnlineView.java b/src/main/java/com/kenshoo/freemarker/view/FreeMarkerOnlineView.java
new file mode 100644
index 0000000..684511f
--- /dev/null
+++ b/src/main/java/com/kenshoo/freemarker/view/FreeMarkerOnlineView.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.view;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.kenshoo.freemarker.model.SelectionOption;
+import com.kenshoo.freemarker.services.AllowedSettingValuesMaps;
+import com.yammer.dropwizard.views.View;
+
+import freemarker.template.Configuration;
+
+/**
+ * Created with IntelliJ IDEA. User: nir Date: 4/11/14 Time: 12:23 PM
+ */
+public class FreeMarkerOnlineView extends View {
+
+    private static final List<SelectionOption> LOCALE_SELECTION_OPTIONS = toLocaleSelectionOptions(AllowedSettingValuesMaps.LOCALE_MAP);
+    private static final List<SelectionOption> TIME_ZONE_SELECTION_OPTIONS = toSelectionOptions(AllowedSettingValuesMaps.TIME_ZONE_MAP);
+    private static final List<SelectionOption> OUTPUT_FORMAT_SELECTION_OPTIONS = toSelectionOptions(AllowedSettingValuesMaps.OUTPUT_FORMAT_MAP);
+    
+    private String template = "";
+    private String dataModel = "";
+    private String outputFormat = AllowedSettingValuesMaps.DEFAULT_OUTPUT_FORMAT_KEY;
+    private String locale = AllowedSettingValuesMaps.DEFAULT_LOCALE_KEY;
+    private String timeZone = AllowedSettingValuesMaps.DEFAULT_TIME_ZONE_KEY;
+    
+    private boolean execute;
+    
+    private static List<SelectionOption> toSelectionOptions(Map<String, ?> settingValueMap) {
+        ArrayList<SelectionOption> selectionOptions = new ArrayList<SelectionOption>(settingValueMap.size());
+        for (String key : settingValueMap.keySet()) {
+            selectionOptions.add(new SelectionOption(key, truncate(key, 25)));
+        }
+        Collections.sort(selectionOptions);
+        return selectionOptions;
+    }
+    
+    private static List<SelectionOption> toLocaleSelectionOptions(Map<String, Locale> localeMap) {
+        ArrayList<SelectionOption> selectionOptions = new ArrayList<SelectionOption>(localeMap.size());
+        for (Map.Entry<String, Locale> ent : localeMap.entrySet()) {
+            Locale locale = ent.getValue();
+            selectionOptions.add(
+                    new SelectionOption(ent.getKey(),
+                    truncate(locale.getDisplayName(Locale.US), 18) + "; " + locale.toString()));
+        }
+        Collections.sort(selectionOptions);
+        return selectionOptions;
+    }
+    
+    private static String truncate(String s, int maxLength) {
+        if (s == null) {
+            return null;
+        }
+        return s.length() <= maxLength ? s : s.substring(0, Math.max(maxLength - 3, 0)) + "[...]";
+    }    
+
+    /**
+     *
+     * @param template
+     * @param dataModel
+     * @param execute set to true if the execution should be triggered on page load.
+     */
+    public FreeMarkerOnlineView() {
+        super("/view/freemarker-online.ftl", Charset.forName("utf-8"));
+    }
+
+    public String getTemplate() {
+        return template;
+    }
+
+    public void setTemplate(String template) {
+        this.template = withDefault(template, "");
+    }
+
+    public String getDataModel() {
+        return dataModel;
+    }
+
+    public void setDataModel(String dataModel) {
+        this.dataModel = withDefault(dataModel, "");
+    }
+
+    public String getFreeMarkerVersion() {
+        return Configuration.getVersion().toString();
+    }
+    
+    public List<SelectionOption> getOutputFormats() {
+        return OUTPUT_FORMAT_SELECTION_OPTIONS;
+    }
+
+    public List<SelectionOption> getLocales() {
+        return LOCALE_SELECTION_OPTIONS;
+    }
+
+    public List<SelectionOption> getTimeZones() {
+        return TIME_ZONE_SELECTION_OPTIONS;
+    }
+
+    public String getOutputFormat() {
+        return outputFormat;
+    }
+
+    public void setOutputFormat(String outputFormat) {
+        this.outputFormat = withDefault(outputFormat, AllowedSettingValuesMaps.DEFAULT_OUTPUT_FORMAT_KEY);
+    }
+
+    public String getLocale() {
+        return locale;
+    }
+
+    public void setLocale(String locale) {
+        this.locale = withDefault(locale, AllowedSettingValuesMaps.DEFAULT_LOCALE_KEY);
+    }
+
+    public String getTimeZone() {
+        return timeZone;
+    }
+
+    public void setTimeZone(String timeZone) {
+        this.timeZone = withDefault(timeZone, AllowedSettingValuesMaps.DEFAULT_TIME_ZONE_KEY);
+    }
+    
+    public boolean isExecute() {
+        return execute;
+    }
+
+    public void setExecute(boolean executeImmediately) {
+        this.execute = executeImmediately;
+    }
+
+    private static String withDefault(String value, String defaultValue) {
+        return !StringUtils.isBlank(value) ? value : defaultValue;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/java/freemarker/core/FreeMarkerInternalsAccessor.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/FreeMarkerInternalsAccessor.java b/src/main/java/freemarker/core/FreeMarkerInternalsAccessor.java
new file mode 100644
index 0000000..1ffc7f7
--- /dev/null
+++ b/src/main/java/freemarker/core/FreeMarkerInternalsAccessor.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * 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 freemarker.core;
+
+import freemarker.core.ThreadInterruptionSupportTemplatePostProcessor.TemplateProcessingThreadInterruptedException;
+import freemarker.template.Template;
+
+/**
+ * Functions that depend on unpublished FreeMarker functionality. Might need to be adjusted for new FreeMarker releases.
+ * The relevant parts of the FreeMarker source code contains comments about keeping this in sync with that, so,
+ * hopefully this won't be a problem.
+ */
+public final class FreeMarkerInternalsAccessor {
+
+    /**
+     * Ensures that the template will react to {@link #interruptTemplateProcessing(Thread)}. 
+     */
+    public static void makeTemplateInterruptable(Template template) {
+        _CoreAPI.addThreadInterruptedChecks(template);
+    }
+
+    /**
+     * Checks if the template processing has thrown exception because of a {@link #interruptTemplateProcessing(Thread)}
+     * call.
+     */
+    public static boolean isTemplateProcessingInterruptedException(Throwable e) {
+        return e instanceof TemplateProcessingThreadInterruptedException;
+    }
+
+    /**
+     * Tells a template processing in another thread to abort; asynchronous.
+     */
+    public static void interruptTemplateProcessing(Thread t) {
+        t.interrupt();
+    }
+
+    /**
+     * Called from the thread where the interruptible template execution ran earlier, to clear any related thread state.
+     */
+    public static void clearAnyPendingTemplateProcessingInterruption() {
+        Thread.interrupted();  // To clears the interruption flag 
+    }
+    
+    private FreeMarkerInternalsAccessor() {
+        // Not meant to be instantiated
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/resources/assets/css/main.css
----------------------------------------------------------------------
diff --git a/src/main/resources/assets/css/main.css b/src/main/resources/assets/css/main.css
new file mode 100644
index 0000000..44b8972
--- /dev/null
+++ b/src/main/resources/assets/css/main.css
@@ -0,0 +1,114 @@
+.clear {
+  clear: both;
+}
+
+#layout {
+	position: relative;
+	padding: 0;
+}
+
+.header {
+	margin: 0;
+	padding: 1em 0;
+	border-bottom: 1px solid #eee;
+	text-align: center;
+	color: #444;
+}
+
+.content {
+    margin: 0 auto;
+    padding: 1em 0;
+    width: 920px;
+}
+
+.footer {
+    margin: 0;
+    padding: 1em 2em 0;
+    border-top: 1px solid #eee;
+    text-align: center;
+    color: #444;
+}
+
+.errorMessage {
+    font-size: small;
+    color: red;
+    display: none;
+}
+
+.header h1 {
+    margin: 0.2em 0;
+    font-size: 1.75em;
+    font-weight: normal;
+}
+
+#result {
+    background-color: #FFF;
+    color: #000	
+}
+
+#result.error {
+    background-color: #FFF0F0;
+	color: #A00;
+}
+
+.faint {
+	color: #bbb
+}
+
+textarea.source-code {
+	font-family: monospace !important;
+    /* We set a few more things to decrease the chance of jQuery Autosize plugin issues: */
+    font-size: 1em !important;  
+	resize: none !important;
+    line-height: 1em !important;
+}
+
+.hiddenByDefault {
+   display: none;
+}
+
+#dataModelExamples {
+	background: #eee;
+	padding: 4px;
+	margin: 0;
+}
+
+#dataModelExamples .description {
+	font-style: italic;
+	margin-bottom: 1em;
+}
+
+#dataModelExamples pre {
+  font-family: monospace;
+  font-size: 1em;
+  padding: 0;
+  margin: 0;
+}
+
+#templateAndModelForm input,
+#templateAndModelForm textarea,
+#templateAndModelForm button {
+	margin-bottom: 1em;
+}
+
+#templateAndModelForm label {
+    margin-top: 1em;
+    margin-bottom: 0.25em;
+}
+
+.formPanel {
+	
+}
+
+.formBottomButtonsContainer {
+    margin-top: 1.5em;
+}
+
+.resultContainer {
+    margin-top: 1em;
+}
+
+.horizontalBox {
+    display: inline-block;
+		padding-right: 1em;	
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/resources/assets/js/autosize.min.js
----------------------------------------------------------------------
diff --git a/src/main/resources/assets/js/autosize.min.js b/src/main/resources/assets/js/autosize.min.js
new file mode 100644
index 0000000..98138c9
--- /dev/null
+++ b/src/main/resources/assets/js/autosize.min.js
@@ -0,0 +1,6 @@
+/*!
+	Autosize 3.0.8
+	license: MIT
+	http://www.jacklmoore.com/autosize
+*/
+!function(e,t){if("function"==typeof define&&define.amd)define(["exports","module"],t);else if("undefined"!=typeof exports&&"undefined"!=typeof module)t(exports,module);else{var o={exports:{}};t(o.exports,o),e.autosize=o.exports}}(this,function(e,t){"use strict";function o(e){function t(){var t=window.getComputedStyle(e,null);"vertical"===t.resize?e.style.resize="none":"both"===t.resize&&(e.style.resize="horizontal"),u="content-box"===t.boxSizing?-(parseFloat(t.paddingTop)+parseFloat(t.paddingBottom)):parseFloat(t.borderTopWidth)+parseFloat(t.borderBottomWidth),i()}function o(t){var o=e.style.width;e.style.width="0px",e.offsetWidth,e.style.width=o,v=t,l&&(e.style.overflowY=t),n()}function n(){var t=window.pageYOffset,o=document.body.scrollTop,n=e.style.height;e.style.height="auto";var i=e.scrollHeight+u;return 0===e.scrollHeight?void(e.style.height=n):(e.style.height=i+"px",document.documentElement.scrollTop=t,void(document.body.scrollTop=o))}function i(){var t=e.style.height;n();va
 r i=window.getComputedStyle(e,null);if(i.height!==e.style.height?"visible"!==v&&o("visible"):"hidden"!==v&&o("hidden"),t!==e.style.height){var r=document.createEvent("Event");r.initEvent("autosize:resized",!0,!1),e.dispatchEvent(r)}}var r=void 0===arguments[1]?{}:arguments[1],d=r.setOverflowX,s=void 0===d?!0:d,a=r.setOverflowY,l=void 0===a?!0:a;if(e&&e.nodeName&&"TEXTAREA"===e.nodeName&&!e.hasAttribute("data-autosize-on")){var u=null,v="hidden",f=function(t){window.removeEventListener("resize",i),e.removeEventListener("input",i),e.removeEventListener("keyup",i),e.removeAttribute("data-autosize-on"),e.removeEventListener("autosize:destroy",f),Object.keys(t).forEach(function(o){e.style[o]=t[o]})}.bind(e,{height:e.style.height,resize:e.style.resize,overflowY:e.style.overflowY,overflowX:e.style.overflowX,wordWrap:e.style.wordWrap});e.addEventListener("autosize:destroy",f),"onpropertychange"in e&&"oninput"in e&&e.addEventListener("keyup",i),window.addEventListener("resize",i),e.addEventL
 istener("input",i),e.addEventListener("autosize:update",i),e.setAttribute("data-autosize-on",!0),l&&(e.style.overflowY="hidden"),s&&(e.style.overflowX="hidden",e.style.wordWrap="break-word"),t()}}function n(e){if(e&&e.nodeName&&"TEXTAREA"===e.nodeName){var t=document.createEvent("Event");t.initEvent("autosize:destroy",!0,!1),e.dispatchEvent(t)}}function i(e){if(e&&e.nodeName&&"TEXTAREA"===e.nodeName){var t=document.createEvent("Event");t.initEvent("autosize:update",!0,!1),e.dispatchEvent(t)}}var r=null;"undefined"==typeof window||"function"!=typeof window.getComputedStyle?(r=function(e){return e},r.destroy=function(e){return e},r.update=function(e){return e}):(r=function(e,t){return e&&Array.prototype.forEach.call(e.length?e:[e],function(e){return o(e,t)}),e},r.destroy=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],n),e},r.update=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],i),e}),t.exports=r});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/resources/assets/js/jquery.autosize.min.js
----------------------------------------------------------------------
diff --git a/src/main/resources/assets/js/jquery.autosize.min.js b/src/main/resources/assets/js/jquery.autosize.min.js
new file mode 100644
index 0000000..5c764dc
--- /dev/null
+++ b/src/main/resources/assets/js/jquery.autosize.min.js
@@ -0,0 +1,6 @@
+/*!
+	Autosize 1.18.17
+	license: MIT
+	http://www.jacklmoore.com/autosize
+*/
+!function(e){var t,o={className:"autosizejs",id:"autosizejs",append:"\n",callback:!1,resizeDelay:10,placeholder:!0},i='<textarea tabindex="-1" style="position:absolute; top:-999px; left:0; right:auto; bottom:auto; border:0; padding: 0; -moz-box-sizing:content-box; -webkit-box-sizing:content-box; box-sizing:content-box; word-wrap:break-word; height:0 !important; min-height:0 !important; overflow:hidden; transition:none; -webkit-transition:none; -moz-transition:none;"/>',a=["fontFamily","fontSize","fontWeight","fontStyle","letterSpacing","textTransform","wordSpacing","textIndent","whiteSpace"],n=e(i).data("autosize",!0)[0];n.style.lineHeight="99px","99px"===e(n).css("lineHeight")&&a.push("lineHeight"),n.style.lineHeight="",e.fn.autosize=function(i){return this.length?(i=e.extend({},o,i||{}),n.parentNode!==document.body&&e(document.body).append(n),this.each(function(){function o(){var t,o=window.getComputedStyle?window.getComputedStyle(u,null):null;o?(t=parseFloat(o.width),("border-box
 "===o.boxSizing||"border-box"===o.webkitBoxSizing||"border-box"===o.mozBoxSizing)&&e.each(["paddingLeft","paddingRight","borderLeftWidth","borderRightWidth"],function(e,i){t-=parseFloat(o[i])})):t=p.width(),n.style.width=Math.max(t,0)+"px"}function s(){var s={};if(t=u,n.className=i.className,n.id=i.id,d=parseFloat(p.css("maxHeight")),e.each(a,function(e,t){s[t]=p.css(t)}),e(n).css(s).attr("wrap",p.attr("wrap")),o(),window.chrome){var r=u.style.width;u.style.width="0px";{u.offsetWidth}u.style.width=r}}function r(){var e,a;t!==u?s():o(),n.value=!u.value&&i.placeholder?p.attr("placeholder")||"":u.value,n.value+=i.append||"",n.style.overflowY=u.style.overflowY,a=parseFloat(u.style.height)||0,n.scrollTop=0,n.scrollTop=9e4,e=n.scrollTop,d&&e>d?(u.style.overflowY="scroll",e=d):(u.style.overflowY="hidden",c>e&&(e=c)),e+=z,Math.abs(a-e)>.01&&(u.style.height=e+"px",n.className=n.className,w&&i.callback.call(u,u),p.trigger("autosize.resized"))}function l(){clearTimeout(h),h=setTimeout(function
 (){var e=p.width();e!==b&&(b=e,r())},parseInt(i.resizeDelay,10))}var d,c,h,u=this,p=e(u),z=0,w=e.isFunction(i.callback),f={height:u.style.height,overflow:u.style.overflow,overflowY:u.style.overflowY,wordWrap:u.style.wordWrap,resize:u.style.resize},b=p.width(),g=p.css("resize");p.data("autosize")||(p.data("autosize",!0),("border-box"===p.css("box-sizing")||"border-box"===p.css("-moz-box-sizing")||"border-box"===p.css("-webkit-box-sizing"))&&(z=p.outerHeight()-p.height()),c=Math.max(parseFloat(p.css("minHeight"))-z||0,p.height()),p.css({overflow:"hidden",overflowY:"hidden",wordWrap:"break-word"}),"vertical"===g?p.css("resize","none"):"both"===g&&p.css("resize","horizontal"),"onpropertychange"in u?"oninput"in u?p.on("input.autosize keyup.autosize",r):p.on("propertychange.autosize",function(){"value"===event.propertyName&&r()}):p.on("input.autosize",r),i.resizeDelay!==!1&&e(window).on("resize.autosize",l),p.on("autosize.resize",r),p.on("autosize.resizeIncludeStyle",function(){t=null,r()
 }),p.on("autosize.destroy",function(){t=null,clearTimeout(h),e(window).off("resize",l),p.off("autosize").off(".autosize").css(f).removeData("autosize")}),r())})):this}}(jQuery||$);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/resources/assets/js/jquery.blockUI.js
----------------------------------------------------------------------
diff --git a/src/main/resources/assets/js/jquery.blockUI.js b/src/main/resources/assets/js/jquery.blockUI.js
new file mode 100644
index 0000000..90ce5d6
--- /dev/null
+++ b/src/main/resources/assets/js/jquery.blockUI.js
@@ -0,0 +1,620 @@
+/*!
+ * jQuery blockUI plugin
+ * Version 2.70.0-2014.11.23
+ * Requires jQuery v1.7 or later
+ *
+ * Examples at: http://malsup.com/jquery/block/
+ * Copyright (c) 2007-2013 M. Alsup
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * Thanks to Amir-Hossein Sobhi for some excellent contributions!
+ */
+
+;(function() {
+/*jshint eqeqeq:false curly:false latedef:false */
+"use strict";
+
+	function setup($) {
+		$.fn._fadeIn = $.fn.fadeIn;
+
+		var noOp = $.noop || function() {};
+
+		// this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle
+		// confusing userAgent strings on Vista)
+		var msie = /MSIE/.test(navigator.userAgent);
+		var ie6  = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent);
+		var mode = document.documentMode || 0;
+		var setExpr = $.isFunction( document.createElement('div').style.setExpression );
+
+		// global $ methods for blocking/unblocking the entire page
+		$.blockUI   = function(opts) { install(window, opts); };
+		$.unblockUI = function(opts) { remove(window, opts); };
+
+		// convenience method for quick growl-like notifications  (http://www.google.com/search?q=growl)
+		$.growlUI = function(title, message, timeout, onClose) {
+			var $m = $('<div class="growlUI"></div>');
+			if (title) $m.append('<h1>'+title+'</h1>');
+			if (message) $m.append('<h2>'+message+'</h2>');
+			if (timeout === undefined) timeout = 3000;
+
+			// Added by konapun: Set timeout to 30 seconds if this growl is moused over, like normal toast notifications
+			var callBlock = function(opts) {
+				opts = opts || {};
+
+				$.blockUI({
+					message: $m,
+					fadeIn : typeof opts.fadeIn  !== 'undefined' ? opts.fadeIn  : 700,
+					fadeOut: typeof opts.fadeOut !== 'undefined' ? opts.fadeOut : 1000,
+					timeout: typeof opts.timeout !== 'undefined' ? opts.timeout : timeout,
+					centerY: false,
+					showOverlay: false,
+					onUnblock: onClose,
+					css: $.blockUI.defaults.growlCSS
+				});
+			};
+
+			callBlock();
+			var nonmousedOpacity = $m.css('opacity');
+			$m.mouseover(function() {
+				callBlock({
+					fadeIn: 0,
+					timeout: 30000
+				});
+
+				var displayBlock = $('.blockMsg');
+				displayBlock.stop(); // cancel fadeout if it has started
+				displayBlock.fadeTo(300, 1); // make it easier to read the message by removing transparency
+			}).mouseout(function() {
+				$('.blockMsg').fadeOut(1000);
+			});
+			// End konapun additions
+		};
+
+		// plugin method for blocking element content
+		$.fn.block = function(opts) {
+			if ( this[0] === window ) {
+				$.blockUI( opts );
+				return this;
+			}
+			var fullOpts = $.extend({}, $.blockUI.defaults, opts || {});
+			this.each(function() {
+				var $el = $(this);
+				if (fullOpts.ignoreIfBlocked && $el.data('blockUI.isBlocked'))
+					return;
+				$el.unblock({ fadeOut: 0 });
+			});
+
+			return this.each(function() {
+				if ($.css(this,'position') == 'static') {
+					this.style.position = 'relative';
+					$(this).data('blockUI.static', true);
+				}
+				this.style.zoom = 1; // force 'hasLayout' in ie
+				install(this, opts);
+			});
+		};
+
+		// plugin method for unblocking element content
+		$.fn.unblock = function(opts) {
+			if ( this[0] === window ) {
+				$.unblockUI( opts );
+				return this;
+			}
+			return this.each(function() {
+				remove(this, opts);
+			});
+		};
+
+		$.blockUI.version = 2.70; // 2nd generation blocking at no extra cost!
+
+		// override these in your code to change the default behavior and style
+		$.blockUI.defaults = {
+			// message displayed when blocking (use null for no message)
+			message:  '<h1>Please wait...</h1>',
+
+			title: null,		// title string; only used when theme == true
+			draggable: true,	// only used when theme == true (requires jquery-ui.js to be loaded)
+
+			theme: false, // set to true to use with jQuery UI themes
+
+			// styles for the message when blocking; if you wish to disable
+			// these and use an external stylesheet then do this in your code:
+			// $.blockUI.defaults.css = {};
+			css: {
+				padding:	0,
+				margin:		0,
+				width:		'30%',
+				top:		'40%',
+				left:		'35%',
+				textAlign:	'center',
+				color:		'#000',
+				border:		'3px solid #aaa',
+				backgroundColor:'#fff',
+				cursor:		'wait'
+			},
+
+			// minimal style set used when themes are used
+			themedCSS: {
+				width:	'30%',
+				top:	'40%',
+				left:	'35%'
+			},
+
+			// styles for the overlay
+			overlayCSS:  {
+				backgroundColor:	'#000',
+				opacity:			0.6,
+				cursor:				'wait'
+			},
+
+			// style to replace wait cursor before unblocking to correct issue
+			// of lingering wait cursor
+			cursorReset: 'default',
+
+			// styles applied when using $.growlUI
+			growlCSS: {
+				width:		'350px',
+				top:		'10px',
+				left:		'',
+				right:		'10px',
+				border:		'none',
+				padding:	'5px',
+				opacity:	0.6,
+				cursor:		'default',
+				color:		'#fff',
+				backgroundColor: '#000',
+				'-webkit-border-radius':'10px',
+				'-moz-border-radius':	'10px',
+				'border-radius':		'10px'
+			},
+
+			// IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
+			// (hat tip to Jorge H. N. de Vasconcelos)
+			/*jshint scripturl:true */
+			iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',
+
+			// force usage of iframe in non-IE browsers (handy for blocking applets)
+			forceIframe: false,
+
+			// z-index for the blocking overlay
+			baseZ: 1000,
+
+			// set these to true to have the message automatically centered
+			centerX: true, // <-- only effects element blocking (page block controlled via css above)
+			centerY: true,
+
+			// allow body element to be stetched in ie6; this makes blocking look better
+			// on "short" pages.  disable if you wish to prevent changes to the body height
+			allowBodyStretch: true,
+
+			// enable if you want key and mouse events to be disabled for content that is blocked
+			bindEvents: true,
+
+			// be default blockUI will supress tab navigation from leaving blocking content
+			// (if bindEvents is true)
+			constrainTabKey: true,
+
+			// fadeIn time in millis; set to 0 to disable fadeIn on block
+			fadeIn:  200,
+
+			// fadeOut time in millis; set to 0 to disable fadeOut on unblock
+			fadeOut:  400,
+
+			// time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
+			timeout: 0,
+
+			// disable if you don't want to show the overlay
+			showOverlay: true,
+
+			// if true, focus will be placed in the first available input field when
+			// page blocking
+			focusInput: true,
+
+            // elements that can receive focus
+            focusableElements: ':input:enabled:visible',
+
+			// suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
+			// no longer needed in 2012
+			// applyPlatformOpacityRules: true,
+
+			// callback method invoked when fadeIn has completed and blocking message is visible
+			onBlock: null,
+
+			// callback method invoked when unblocking has completed; the callback is
+			// passed the element that has been unblocked (which is the window object for page
+			// blocks) and the options that were passed to the unblock call:
+			//	onUnblock(element, options)
+			onUnblock: null,
+
+			// callback method invoked when the overlay area is clicked.
+			// setting this will turn the cursor to a pointer, otherwise cursor defined in overlayCss will be used.
+			onOverlayClick: null,
+
+			// don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
+			quirksmodeOffsetHack: 4,
+
+			// class name of the message block
+			blockMsgClass: 'blockMsg',
+
+			// if it is already blocked, then ignore it (don't unblock and reblock)
+			ignoreIfBlocked: false
+		};
+
+		// private data and functions follow...
+
+		var pageBlock = null;
+		var pageBlockEls = [];
+
+		function install(el, opts) {
+			var css, themedCSS;
+			var full = (el == window);
+			var msg = (opts && opts.message !== undefined ? opts.message : undefined);
+			opts = $.extend({}, $.blockUI.defaults, opts || {});
+
+			if (opts.ignoreIfBlocked && $(el).data('blockUI.isBlocked'))
+				return;
+
+			opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
+			css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
+			if (opts.onOverlayClick)
+				opts.overlayCSS.cursor = 'pointer';
+
+			themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {});
+			msg = msg === undefined ? opts.message : msg;
+
+			// remove the current block (if there is one)
+			if (full && pageBlock)
+				remove(window, {fadeOut:0});
+
+			// if an existing element is being used as the blocking content then we capture
+			// its current place in the DOM (and current display style) so we can restore
+			// it when we unblock
+			if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
+				var node = msg.jquery ? msg[0] : msg;
+				var data = {};
+				$(el).data('blockUI.history', data);
+				data.el = node;
+				data.parent = node.parentNode;
+				data.display = node.style.display;
+				data.position = node.style.position;
+				if (data.parent)
+					data.parent.removeChild(node);
+			}
+
+			$(el).data('blockUI.onUnblock', opts.onUnblock);
+			var z = opts.baseZ;
+
+			// blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
+			// layer1 is the iframe layer which is used to supress bleed through of underlying content
+			// layer2 is the overlay layer which has opacity and a wait cursor (by default)
+			// layer3 is the message content that is displayed while blocking
+			var lyr1, lyr2, lyr3, s;
+			if (msie || opts.forceIframe)
+				lyr1 = $('<iframe class="blockUI" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+opts.iframeSrc+'"></iframe>');
+			else
+				lyr1 = $('<div class="blockUI" style="display:none"></div>');
+
+			if (opts.theme)
+				lyr2 = $('<div class="blockUI blockOverlay ui-widget-overlay" style="z-index:'+ (z++) +';display:none"></div>');
+			else
+				lyr2 = $('<div class="blockUI blockOverlay" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>');
+
+			if (opts.theme && full) {
+				s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:fixed">';
+				if ( opts.title ) {
+					s += '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>';
+				}
+				s += '<div class="ui-widget-content ui-dialog-content"></div>';
+				s += '</div>';
+			}
+			else if (opts.theme) {
+				s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:absolute">';
+				if ( opts.title ) {
+					s += '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>';
+				}
+				s += '<div class="ui-widget-content ui-dialog-content"></div>';
+				s += '</div>';
+			}
+			else if (full) {
+				s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage" style="z-index:'+(z+10)+';display:none;position:fixed"></div>';
+			}
+			else {
+				s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement" style="z-index:'+(z+10)+';display:none;position:absolute"></div>';
+			}
+			lyr3 = $(s);
+
+			// if we have a message, style it
+			if (msg) {
+				if (opts.theme) {
+					lyr3.css(themedCSS);
+					lyr3.addClass('ui-widget-content');
+				}
+				else
+					lyr3.css(css);
+			}
+
+			// style the overlay
+			if (!opts.theme /*&& (!opts.applyPlatformOpacityRules)*/)
+				lyr2.css(opts.overlayCSS);
+			lyr2.css('position', full ? 'fixed' : 'absolute');
+
+			// make iframe layer transparent in IE
+			if (msie || opts.forceIframe)
+				lyr1.css('opacity',0.0);
+
+			//$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);
+			var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el);
+			$.each(layers, function() {
+				this.appendTo($par);
+			});
+
+			if (opts.theme && opts.draggable && $.fn.draggable) {
+				lyr3.draggable({
+					handle: '.ui-dialog-titlebar',
+					cancel: 'li'
+				});
+			}
+
+			// ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
+			var expr = setExpr && (!$.support.boxModel || $('object,embed', full ? null : el).length > 0);
+			if (ie6 || expr) {
+				// give body 100% height
+				if (full && opts.allowBodyStretch && $.support.boxModel)
+					$('html,body').css('height','100%');
+
+				// fix ie6 issue when blocked element has a border width
+				if ((ie6 || !$.support.boxModel) && !full) {
+					var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
+					var fixT = t ? '(0 - '+t+')' : 0;
+					var fixL = l ? '(0 - '+l+')' : 0;
+				}
+
+				// simulate fixed position
+				$.each(layers, function(i,o) {
+					var s = o[0].style;
+					s.position = 'absolute';
+					if (i < 2) {
+						if (full)
+							s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"');
+						else
+							s.setExpression('height','this.parentNode.offsetHeight + "px"');
+						if (full)
+							s.setExpression('width','jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"');
+						else
+							s.setExpression('width','this.parentNode.offsetWidth + "px"');
+						if (fixL) s.setExpression('left', fixL);
+						if (fixT) s.setExpression('top', fixT);
+					}
+					else if (opts.centerY) {
+						if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"');
+						s.marginTop = 0;
+					}
+					else if (!opts.centerY && full) {
+						var top = (opts.css && opts.css.top) ? parseInt(opts.css.top, 10) : 0;
+						var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"';
+						s.setExpression('top',expression);
+					}
+				});
+			}
+
+			// show the message
+			if (msg) {
+				if (opts.theme)
+					lyr3.find('.ui-widget-content').append(msg);
+				else
+					lyr3.append(msg);
+				if (msg.jquery || msg.nodeType)
+					$(msg).show();
+			}
+
+			if ((msie || opts.forceIframe) && opts.showOverlay)
+				lyr1.show(); // opacity is zero
+			if (opts.fadeIn) {
+				var cb = opts.onBlock ? opts.onBlock : noOp;
+				var cb1 = (opts.showOverlay && !msg) ? cb : noOp;
+				var cb2 = msg ? cb : noOp;
+				if (opts.showOverlay)
+					lyr2._fadeIn(opts.fadeIn, cb1);
+				if (msg)
+					lyr3._fadeIn(opts.fadeIn, cb2);
+			}
+			else {
+				if (opts.showOverlay)
+					lyr2.show();
+				if (msg)
+					lyr3.show();
+				if (opts.onBlock)
+					opts.onBlock.bind(lyr3)();
+			}
+
+			// bind key and mouse events
+			bind(1, el, opts);
+
+			if (full) {
+				pageBlock = lyr3[0];
+				pageBlockEls = $(opts.focusableElements,pageBlock);
+				if (opts.focusInput)
+					setTimeout(focus, 20);
+			}
+			else
+				center(lyr3[0], opts.centerX, opts.centerY);
+
+			if (opts.timeout) {
+				// auto-unblock
+				var to = setTimeout(function() {
+					if (full)
+						$.unblockUI(opts);
+					else
+						$(el).unblock(opts);
+				}, opts.timeout);
+				$(el).data('blockUI.timeout', to);
+			}
+		}
+
+		// remove the block
+		function remove(el, opts) {
+			var count;
+			var full = (el == window);
+			var $el = $(el);
+			var data = $el.data('blockUI.history');
+			var to = $el.data('blockUI.timeout');
+			if (to) {
+				clearTimeout(to);
+				$el.removeData('blockUI.timeout');
+			}
+			opts = $.extend({}, $.blockUI.defaults, opts || {});
+			bind(0, el, opts); // unbind events
+
+			if (opts.onUnblock === null) {
+				opts.onUnblock = $el.data('blockUI.onUnblock');
+				$el.removeData('blockUI.onUnblock');
+			}
+
+			var els;
+			if (full) // crazy selector to handle odd field errors in ie6/7
+				els = $('body').children().filter('.blockUI').add('body > .blockUI');
+			else
+				els = $el.find('>.blockUI');
+
+			// fix cursor issue
+			if ( opts.cursorReset ) {
+				if ( els.length > 1 )
+					els[1].style.cursor = opts.cursorReset;
+				if ( els.length > 2 )
+					els[2].style.cursor = opts.cursorReset;
+			}
+
+			if (full)
+				pageBlock = pageBlockEls = null;
+
+			if (opts.fadeOut) {
+				count = els.length;
+				els.stop().fadeOut(opts.fadeOut, function() {
+					if ( --count === 0)
+						reset(els,data,opts,el);
+				});
+			}
+			else
+				reset(els, data, opts, el);
+		}
+
+		// move blocking element back into the DOM where it started
+		function reset(els,data,opts,el) {
+			var $el = $(el);
+			if ( $el.data('blockUI.isBlocked') )
+				return;
+
+			els.each(function(i,o) {
+				// remove via DOM calls so we don't lose event handlers
+				if (this.parentNode)
+					this.parentNode.removeChild(this);
+			});
+
+			if (data && data.el) {
+				data.el.style.display = data.display;
+				data.el.style.position = data.position;
+				data.el.style.cursor = 'default'; // #59
+				if (data.parent)
+					data.parent.appendChild(data.el);
+				$el.removeData('blockUI.history');
+			}
+
+			if ($el.data('blockUI.static')) {
+				$el.css('position', 'static'); // #22
+			}
+
+			if (typeof opts.onUnblock == 'function')
+				opts.onUnblock(el,opts);
+
+			// fix issue in Safari 6 where block artifacts remain until reflow
+			var body = $(document.body), w = body.width(), cssW = body[0].style.width;
+			body.width(w-1).width(w);
+			body[0].style.width = cssW;
+		}
+
+		// bind/unbind the handler
+		function bind(b, el, opts) {
+			var full = el == window, $el = $(el);
+
+			// don't bother unbinding if there is nothing to unbind
+			if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
+				return;
+
+			$el.data('blockUI.isBlocked', b);
+
+			// don't bind events when overlay is not in use or if bindEvents is false
+			if (!full || !opts.bindEvents || (b && !opts.showOverlay))
+				return;
+
+			// bind anchors and inputs for mouse and key events
+			var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove';
+			if (b)
+				$(document).bind(events, opts, handler);
+			else
+				$(document).unbind(events, handler);
+
+		// former impl...
+		//		var $e = $('a,:input');
+		//		b ? $e.bind(events, opts, handler) : $e.unbind(events, handler);
+		}
+
+		// event handler to suppress keyboard/mouse events when blocking
+		function handler(e) {
+			// allow tab navigation (conditionally)
+			if (e.type === 'keydown' && e.keyCode && e.keyCode == 9) {
+				if (pageBlock && e.data.constrainTabKey) {
+					var els = pageBlockEls;
+					var fwd = !e.shiftKey && e.target === els[els.length-1];
+					var back = e.shiftKey && e.target === els[0];
+					if (fwd || back) {
+						setTimeout(function(){focus(back);},10);
+						return false;
+					}
+				}
+			}
+			var opts = e.data;
+			var target = $(e.target);
+			if (target.hasClass('blockOverlay') && opts.onOverlayClick)
+				opts.onOverlayClick(e);
+
+			// allow events within the message content
+			if (target.parents('div.' + opts.blockMsgClass).length > 0)
+				return true;
+
+			// allow events for content that is not being blocked
+			return target.parents().children().filter('div.blockUI').length === 0;
+		}
+
+		function focus(back) {
+			if (!pageBlockEls)
+				return;
+			var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
+			if (e)
+				e.focus();
+		}
+
+		function center(el, x, y) {
+			var p = el.parentNode, s = el.style;
+			var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
+			var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
+			if (x) s.left = l > 0 ? (l+'px') : '0';
+			if (y) s.top  = t > 0 ? (t+'px') : '0';
+		}
+
+		function sz(el, p) {
+			return parseInt($.css(el,p),10)||0;
+		}
+
+	}
+
+
+	/*global define:true */
+	if (typeof define === 'function' && define.amd && define.amd.jQuery) {
+		define(['jquery'], setup);
+	} else {
+		setup(jQuery);
+	}
+
+})();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/resources/assets/js/script.js
----------------------------------------------------------------------
diff --git a/src/main/resources/assets/js/script.js b/src/main/resources/assets/js/script.js
new file mode 100644
index 0000000..7e88910
--- /dev/null
+++ b/src/main/resources/assets/js/script.js
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ *
+ * 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.
+ */
+
+/**
+ * Created by Pmuruge on 8/28/2015.
+ */
+$(document).ready(function() {
+    $("#eval-btn").click(function() {
+        execute();
+    });
+    $('#templateAndModelForm textarea, #templateAndModelForm select').keydown(function (e) {
+        if ((e.keyCode == 10 || e.keyCode == 13) && e.ctrlKey) {
+            execute();
+        }
+    });
+    $.blockUI.defaults.fadeIn = 1000;
+    $.blockUI.defaults.fadeOut = 0;
+});
+
+var hasPendingExecuteAjaxCall = false;
+
+function execute() {
+    if (hasPendingExecuteAjaxCall || !checkFormSendable()) {
+        return;
+    }
+    
+    var request = {
+        "template": $("#template").val(),
+        "dataModel": $("#dataModel").val(),
+        "outputFormat": $("#outputFormat").val(),
+        "locale": $("#locale").val(),
+        "timeZone": $("#timeZone").val()
+    }
+
+    $.ajax({
+        method: "POST",
+        url: "/api/execute",
+        data: JSON.stringify(request),
+        headers: { "Content-Type":"application/json" },
+        beforeSend: function (jqXHR, options) {
+            hasPendingExecuteAjaxCall = true;
+            $.blockUI({ message: null });
+            $("#error").hide();
+            return true;
+        }    
+    })
+    .done(function (data) {
+        if (data.problems && data.problems.length != 0) {
+            showResult(data.problems[0].message, true);              
+        } else {
+            showResult(data.result, false);              
+        }
+    })
+    .fail(function (data) {
+        if (data.responseJSON) {
+            showResult(data.responseJSON.errorCode + ": " + data.responseJSON.errorDescription, true);              
+        } else {
+            showResult("The service was unavailable or had returned an invalid response.", true);              
+        }
+    })
+    .always(function (data) {
+        hasPendingExecuteAjaxCall = false;
+        $.unblockUI();
+    });
+}
+
+function checkFormSendable() {
+    if($.trim($("#template").val()) === "" ) {
+        showResult("Template was empty; nothing to do.", true);
+        return false;
+    }
+    return true;
+}
+
+function showResult(result, isError) {
+    if (isError) {
+        $("#result").addClass("error");
+    } else {
+        $("#result").removeClass("error");
+    }
+    $("#result").val(result);
+    $(".resultContainer").show();
+    autosize.update($("#result"));
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/resources/banner.txt
----------------------------------------------------------------------
diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt
new file mode 100644
index 0000000..784711d
--- /dev/null
+++ b/src/main/resources/banner.txt
@@ -0,0 +1,10 @@
+
+  ______                                  _                 ____        _ _            
+ |  ____|                                | |               / __ \      | (_)           
+ | |__ _ __ ___  ___ _ __ ___   __ _ _ __| | _____ _ __   | |  | |_ __ | |_ _ __   ___ 
+ |  __| '__/ _ \/ _ \ '_ ` _ \ / _` | '__| |/ / _ \ '__|  | |  | | '_ \| | | '_ \ / _ \
+ | |  | | |  __/  __/ | | | | | (_| | |  |   <  __/ |     | |__| | | | | | | | | |  __/
+ |_|  |_|  \___|\___|_| |_| |_|\__,_|_|  |_|\_\___|_|      \____/|_| |_|_|_|_| |_|\___|
+                                                                                       
+                                                                                       
+

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/resources/freemarker-online.yml
----------------------------------------------------------------------
diff --git a/src/main/resources/freemarker-online.yml b/src/main/resources/freemarker-online.yml
new file mode 100644
index 0000000..370dd91
--- /dev/null
+++ b/src/main/resources/freemarker-online.yml
@@ -0,0 +1,22 @@
+# Spring configuration
+# Application Contexts to Load.
+applicationContext: ['classpath*:/spring/*-context.xml']
+
+logging:
+       # Settings for logging to a file.
+       file:
+            # If true, write log statements to a file.
+            enabled: true
+            threshold: ALL
+            # The file to which current statements will be logged.
+            currentLogFilename: /var/log/freemarker-online/freemarker-online.log
+
+            # When the log file rotates, the archived log will be renamed to this and gzipped. The
+            # %d is replaced with the previous day (yyyy-MM-dd). Custom rolling windows can be created
+            # by passing a SimpleDateFormat-compatible format as an argument: "%d{yyyy-MM-dd-hh}".
+            archivedLogFilenamePattern: /var/log/freemarker-online/freemarker-online-%d.log.gz
+            # The number of archived files to keep.
+            archivedFileCount: 5
+
+            # The timezone used to format dates. HINT: USE THE DEFAULT, UTC.
+            timeZone: UTC

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/resources/spring/bootstrap-context.xml
----------------------------------------------------------------------
diff --git a/src/main/resources/spring/bootstrap-context.xml b/src/main/resources/spring/bootstrap-context.xml
new file mode 100644
index 0000000..2ea0c33
--- /dev/null
+++ b/src/main/resources/spring/bootstrap-context.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd ">
+
+    <context:component-scan base-package="com.kenshoo"/>
+
+    <context:annotation-config/>
+
+    <!-- Use the system properties (initalized by DW) to configure spring context files -->
+    <bean id="propertyPlaceholderConfigurer"
+          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
+        <property name="ignoreUnresolvablePlaceholders" value="true"/>
+        <property name="ignoreResourceNotFound" value="true"/>
+    </bean>
+
+</beans>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/resources/view/freemarker-online.ftl
----------------------------------------------------------------------
diff --git a/src/main/resources/view/freemarker-online.ftl b/src/main/resources/view/freemarker-online.ftl
new file mode 100644
index 0000000..57746ff
--- /dev/null
+++ b/src/main/resources/view/freemarker-online.ftl
@@ -0,0 +1,129 @@
+<#ftl outputFormat="HTML">
+<#import "utils.ftl" as u>
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <link rel="stylesheet" href="css/main.css">
+    <link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.5.0/pure-min.css">
+    
+    <script src="https://code.jquery.com/jquery-1.11.2.min.js"></script>
+    <script src="js/jquery.blockUI.js"></script>
+    <script src="js/autosize.min.js"></script>
+    <script src="js/script.js"></script>
+    <script>
+        $(function() {
+            // Auto-focus on first form input:
+            $('#templateAndModelForm *:input[type!=hidden]:first').focus();
+            
+            // Submit form when Ctrl+Enter is hit in a textarea:            
+
+        
+            // Dynamically adapt text areas heights to their content:
+            //$('#templateAndModelForm textarea').autosize();
+            autosize($('textarea'));
+        
+            // Show/hide data model examples:
+            $("#showHideDataModelExamples").click(function(e) {
+            	 $("#dataModelExamples").toggle();
+           		 $("#hideDataModelExamplesLabel").toggle();
+                 $("#showDataModelExamplesLabel").toggle();
+            	 
+                 e.preventDefault();
+            	 return false;
+            })
+            <#if execute>
+                execute();
+            </#if>
+        });
+    </script>
+    
+    <title>Online FreeMarker Template Tester</title>
+</head>
+<body>
+<div id="layout">
+    <div id="main">
+        <div class="header">
+            <h1>Online FreeMarker Template Tester</h1>
+        </div>
+
+        <div class="content">
+            <!--[if lte IE 8]>
+            <div style="background-color: #C00; color: #fff; padding: 12px 24px;">
+              You seem to use Internet Explorer 8 or older. This page might won't work properly with that.
+            </div>
+            <![endif]-->
+          
+            <form id="templateAndModelForm" method="post" class="pure-form pure-form-stacked">
+                <label for="template">Template <span class="faint">(Apache FreeMarker ${freeMarkerVersion})</span></label>
+                <textarea id="template" name="template" class="pure-input-1 source-code"
+                        placeholder="Enter template, like: Hello ${r'${user}'}!"
+                >${template}</textarea>
+    
+                <label for="template">
+                    Data model
+                    (<a id="showHideDataModelExamples" href="#" tabindex="-1"><!--
+                    --><span id="showDataModelExamplesLabel">show</span><!--
+                    --><span id="hideDataModelExamplesLabel" class="hiddenByDefault">hide</span>
+                    examples</a>)
+                </label>
+                <div id="dataModelExamples" class="hiddenByDefault">
+                  <div class="description">
+                      Note: This syntax is specific to this online service; normally, you just have Java objects as
+                      data-model.
+                  </div>
+                  <pre>someString = Some value
+otherString = "JSON\nsyntax"
+someNumber = 3.14
+someBoolean = true
+someDate = 2014-02-28
+someTime = 20:50:30.5+02:00
+someDatetime = 2014-02-28T18:50Z
+someList = ["JSON", "syntax", 1, 2, 3 ]
+someMap = { "JSON syntax": true, "nestedList": [1, 2, 3] }
+someXML = &lt;example x="1"&gt;text&lt;/example&gt;</pre></div>
+                <textarea id="dataModel" name="dataModel" class="pure-input-1 source-code"
+                        placeholder='Enter one or more assignments (e.g., user = John Doe), starting each in its own line.'
+                >${dataModel}</textarea>
+                <div class="formPanel">
+                  <div class="horizontalBox">
+                    <@u.htmlSelect caption="Output format" name="outputFormat" selectionOptions=outputFormats />
+                  </div>
+                  <div class="horizontalBox">
+                    <@u.htmlSelect caption="Locale" name="locale" selectionOptions=locales />
+                  </div>
+                  <div class="horizontalBox">
+                    <@u.htmlSelect caption="Time zone" name="timeZone" selectionOptions=timeZones />
+                  </div>
+                </div>
+                <div class="formBottomButtonsContainer">
+	                <input id="eval-btn" type="button" value="Evaluate" class="pure-button pure-button-primary"/>
+	                &nbsp; <span class="faint">Ctrl+Enter in input fields will submit this form too</span>
+                </div>
+                <div style="display:none" class="resultContainer">
+                    <label for="result">Result</label>
+                    <textarea id="result" class="pure-input-1 source-code" readonly></textarea>
+                </div>
+
+            </form>
+        </div><!-- content -->
+        
+        <div class="footer">
+            FreeMarker documentation:
+            <a href="http://freemarker.org/docs/" target="_blank">Contents</a>
+            |
+            <a href="http://freemarker.org/docs/dgui_template_overallstructure.html" target="_blank">Overall&nbsp;syntax</a>
+            |
+            <a href="http://freemarker.org/docs/dgui_template_exp.html#exp_cheatsheet" target="_blank">Expression&nbsp;syntax</a>
+            |
+            <a href="http://freemarker.org/docs/ref_directive_alphaidx.html" target="_blank">List&nbsp;of&nbsp;&lt;#<i>directives</i>&gt;</a>
+            |
+            <a href="http://freemarker.org/docs/ref_builtins_alphaidx.html" target="_blank">List&nbsp;of&nbsp;<tt>?<i>built_in</i></tt> functions</a>
+        </div><!-- footer -->
+    </div><!-- main -->
+    
+    <!-- Fork me on GitHub: -->
+    <a href="https://github.com/kenshoo/freemarker-online" target="_blank"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/a6677b08c955af8400f44c6298f40e7d19cc5b2d/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f677261795f3664366436642e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png"></a>
+</div><!-- layout -->
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/main/resources/view/utils.ftl
----------------------------------------------------------------------
diff --git a/src/main/resources/view/utils.ftl b/src/main/resources/view/utils.ftl
new file mode 100644
index 0000000..cfa5ee6
--- /dev/null
+++ b/src/main/resources/view/utils.ftl
@@ -0,0 +1,13 @@
+<#ftl outputFormat='HTML'>
+
+<#macro htmlSelect caption name selectionOptions>
+  <div>${caption}:</div>
+  <div> 
+    <select name="${name}" id="${name}" class="pure-input-1">
+      <#list selectionOptions as selectionOption>
+        <#local value = selectionOption.value>
+        <option value="${value}"<#if value == .vars[name]!> selected</#if>>${selectionOption.label}</option>
+      </#list>
+    </select>
+  </div>
+</#macro>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/test/java/com/kenshoo/freemarker/platform/DropWizardServiceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/com/kenshoo/freemarker/platform/DropWizardServiceTest.java b/src/test/java/com/kenshoo/freemarker/platform/DropWizardServiceTest.java
new file mode 100644
index 0000000..38f7e88
--- /dev/null
+++ b/src/test/java/com/kenshoo/freemarker/platform/DropWizardServiceTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.platform;
+
+import com.google.common.io.Resources;
+import com.kenshoo.freemarker.dropwizard.ApplicationStartup;
+import com.yammer.dropwizard.testing.junit.DropwizardServiceRule;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: shlomis
+ * Date: 9/9/13
+ * Time: 10:43 AM
+ */
+public class DropWizardServiceTest {
+    @ClassRule
+    public static TestRule testRule = new DropwizardServiceRule<>(ApplicationStartup.class,
+            Resources.getResource("freemarker-online.yml").getPath());
+
+
+    @Test
+    public void testServerIsUp() throws Exception {
+        ((DropwizardServiceRule) testRule).getService();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/test/java/com/kenshoo/freemarker/platform/YamlPropertiesPersister.java
----------------------------------------------------------------------
diff --git a/src/test/java/com/kenshoo/freemarker/platform/YamlPropertiesPersister.java b/src/test/java/com/kenshoo/freemarker/platform/YamlPropertiesPersister.java
new file mode 100644
index 0000000..e01a881
--- /dev/null
+++ b/src/test/java/com/kenshoo/freemarker/platform/YamlPropertiesPersister.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kenshoo.freemarker.platform;
+
+import com.fasterxml.jackson.dataformat.yaml.snakeyaml.Yaml;
+import org.springframework.util.PropertiesPersister;
+import org.springframework.util.StringUtils;
+
+import java.io.*;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: shlomis
+ * Date: 9/8/13
+ * Time: 10:50 PM
+ */
+public class YamlPropertiesPersister implements PropertiesPersister {
+    @Override
+    public void load(Properties props, InputStream is) throws IOException {
+        load(props, new InputStreamReader(is));
+    }
+
+    /**
+     * We want to traverse map representing Yaml object and each time we find String=String pair we want to
+     * save it as Property. As we are going deeper into map we generate compound key as path-like String
+     *
+     * @see org.springframework.util.PropertiesPersister#load(java.util.Properties, java.io.Reader)
+     */
+    @Override
+    public void load(Properties props, Reader reader) throws IOException {
+        Yaml yaml = new Yaml();
+        @SuppressWarnings("unchecked")
+        Map<String, Object> map = (Map<String, Object>) yaml.load(reader);
+        // now we can populate supplied props
+        assignProperties(props, map, null);
+    }
+
+    @SuppressWarnings("unchecked")
+    public void assignProperties(Properties props, Map<String, Object> map, String path) {
+        for (Map.Entry<String, Object> entry : map.entrySet()) {
+            String key = entry.getKey();
+            if (!StringUtils.isEmpty(path))
+                key = path + "." + key;
+            Object val = entry.getValue();
+            if (val instanceof String) {
+                // see if we need to create a compound key
+                props.put(key, val);
+            } else if (val instanceof Map) {
+                assignProperties(props, (Map<String, Object>) val, key);
+            }
+        }
+    }
+
+    @Override
+    public void store(Properties props, OutputStream os, String header) throws IOException {
+        throw new IllegalStateException("Current implementation is a read-only");
+    }
+
+    @Override
+    public void store(Properties props, Writer writer, String header) throws IOException {
+        throw new IllegalStateException("Current implementation is a read-only");
+    }
+
+    @Override
+    public void loadFromXml(Properties props, InputStream is) throws IOException {
+        throw new IllegalStateException("Use DefaultPropertiesPersister if you want to read/write XML");
+    }
+
+    @Override
+    public void storeToXml(Properties props, OutputStream os, String header) throws IOException {
+        throw new IllegalStateException("Use DefaultPropertiesPersister if you want to load/store to XML");
+    }
+
+    @Override
+    public void storeToXml(Properties props, OutputStream os, String header, String encoding) throws IOException {
+        throw new IllegalStateException("Use DefaultPropertiesPersister if you want to read/write XML");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/abb26297/src/test/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineExecuteResourceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineExecuteResourceTest.java b/src/test/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineExecuteResourceTest.java
new file mode 100644
index 0000000..f4886c8
--- /dev/null
+++ b/src/test/java/com/kenshoo/freemarker/resources/FreeMarkerOnlineExecuteResourceTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2014 Kenshoo.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.kenshoo.freemarker.resources;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.springframework.web.context.ContextLoaderListener;
+import org.springframework.web.context.request.RequestContextListener;
+
+import com.kenshoo.freemarker.model.ExecuteRequest;
+import com.kenshoo.freemarker.model.ExecuteResourceField;
+import com.kenshoo.freemarker.model.ExecuteResourceProblem;
+import com.kenshoo.freemarker.model.ExecuteResponse;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.spi.spring.container.servlet.SpringServlet;
+import com.sun.jersey.test.framework.AppDescriptor;
+import com.sun.jersey.test.framework.JerseyTest;
+import com.sun.jersey.test.framework.WebAppDescriptor;
+
+/**
+ * Created by Pmuruge on 8/29/2015.
+ */
+public class FreeMarkerOnlineExecuteResourceTest extends JerseyTest {
+    private static final String DATA_MODEL = "user=John";
+    private static final String TEMPLATE_WITH_VARIABLE = "Welcome ${user}";
+    private static final String TEMPLATE_PLAIN = "Welcome John";
+    private static final String MALFORMED_DATA_MODEL = "userJohn";
+    private static final String EXECUTE_API = "api/execute";
+    @Override
+    protected AppDescriptor configure() {
+        return new WebAppDescriptor.Builder("com.kenshoo.freemarker.resources")
+                        .contextPath("/")
+                        .contextListenerClass(ContextLoaderListener.class)
+                        .contextParam("contextConfigLocation", "classpath:spring/bootstrap-context.xml")
+                        .servletClass(SpringServlet.class)
+                        .requestListenerClass(RequestContextListener.class)
+                        .build();
+    }
+
+    @Test
+    public void testSuccessRequest() throws Exception {
+        ExecuteRequest req = new ExecuteRequest(TEMPLATE_WITH_VARIABLE, DATA_MODEL);
+        ClientResponse resp = client().resource(getBaseURI().toString() + EXECUTE_API)
+                .header("Content-Type", "application/json").entity(req).post(ClientResponse.class);
+        assertEquals(200, resp.getStatus());
+        ExecuteResponse response = resp.getEntity(ExecuteResponse.class);
+        assertNull(response.getProblems());
+    }
+
+    @Test
+    public void testMalformedDataModel() throws Exception {
+        ExecuteRequest req = new ExecuteRequest(TEMPLATE_PLAIN, MALFORMED_DATA_MODEL);
+        ClientResponse resp = client().resource(getBaseURI().toString() + EXECUTE_API)
+                .header("Content-Type", "application/json").entity(req).post(ClientResponse.class);
+        assertEquals(200, resp.getStatus());
+        ExecuteResponse response = resp.getEntity(ExecuteResponse.class);
+        assertNotNull(response.getProblems());
+        assertTrue(containsProblem(response, ExecuteResourceField.DATA_MODEL));
+    }
+
+    @Test
+    public void testLongDataModel() throws Exception {
+        ExecuteRequest req = new ExecuteRequest(TEMPLATE_PLAIN, create30KString());
+        ClientResponse resp = client().resource(getBaseURI().toString() + EXECUTE_API)
+                .header("Content-Type", "application/json").entity(req).post(ClientResponse.class);
+        assertEquals(200, resp.getStatus());
+        ExecuteResponse response = resp.getEntity(ExecuteResponse.class);
+        assertNotNull(response.getProblems());
+        assertTrue(containsProblem(response, ExecuteResourceField.DATA_MODEL));
+        String problemMessage = getProblemMessage(response, ExecuteResourceField.DATA_MODEL);
+        assertThat(problemMessage, containsString("data model"));
+        assertThat(problemMessage, containsString("limit"));
+    }
+
+    @Test
+    public void testLongTemplate() throws Exception {
+        ExecuteRequest req = new ExecuteRequest(create30KString(), DATA_MODEL);
+        ClientResponse resp = client().resource(getBaseURI().toString() + EXECUTE_API)
+                .header("Content-Type", "application/json").entity(req).post(ClientResponse.class);
+        assertEquals(200, resp.getStatus());
+        ExecuteResponse response = resp.getEntity(ExecuteResponse.class);
+        assertNotNull(response.getProblems());
+        assertTrue(containsProblem(response, ExecuteResourceField.TEMPLATE));
+        String problemMessage = getProblemMessage(response, ExecuteResourceField.TEMPLATE);
+        assertThat(problemMessage, containsString("template"));
+        assertThat(problemMessage, containsString("limit"));
+    }
+
+    @Test
+    public void testMultipleErrorsDataModel() throws Exception {
+        ExecuteRequest req = new ExecuteRequest(create30KString(), create30KString());
+        req.setOutputFormat("wrongOutputFormat");
+        req.setLocale("wrongLocale");
+        req.setTimeZone("wrongTimeZone");
+        
+        ClientResponse resp = client().resource(getBaseURI() + EXECUTE_API)
+                .header("Content-Type", "application/json").entity(req).post(ClientResponse.class);
+        
+        assertEquals(200, resp.getStatus());
+        ExecuteResponse response = resp.getEntity(ExecuteResponse.class);
+        assertNotNull(response.getProblems());
+        assertThat(getProblemMessage(response, ExecuteResourceField.TEMPLATE), containsString("limit"));
+        assertThat(getProblemMessage(response, ExecuteResourceField.DATA_MODEL), containsString("limit"));
+        assertThat(getProblemMessage(response, ExecuteResourceField.OUTPUT_FORMAT), containsString("wrongOutputFormat"));
+        assertThat(getProblemMessage(response, ExecuteResourceField.LOCALE), containsString("wrongLocale"));
+        assertThat(getProblemMessage(response, ExecuteResourceField.TIME_ZONE), containsString("wrongTimeZone"));
+    }
+    
+    private String create30KString() {
+        final String veryLongString;
+        {
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < 30000 / 10; i++) {
+                sb.append("0123456789");
+            }
+            veryLongString = sb.toString();
+        }
+        return veryLongString;
+    }
+
+    private boolean containsProblem(ExecuteResponse response, ExecuteResourceField field) {
+        for (ExecuteResourceProblem problem : response.getProblems()) {
+            if (problem.getField() == field) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private String getProblemMessage(ExecuteResponse response, ExecuteResourceField field) {
+        for (ExecuteResourceProblem problem : response.getProblems()) {
+            if (problem.getField() == field) {
+                return problem.getMessage();
+            }
+        }
+        return null;
+    }
+    
+}