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/07/04 07:44:20 UTC

[09/16] incubator-freemarker git commit: Adding FreemarkerViewResolver and improving FreemarkerViewTest with JspTaglibFactory and example.

Adding FreemarkerViewResolver and improving FreemarkerViewTest with JspTaglibFactory and example.


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

Branch: refs/heads/3
Commit: 9083de1ba04d9d54b80109ed86941da72337cba5
Parents: 83e896c
Author: Woonsan Ko <wo...@apache.org>
Authored: Sun Jul 2 01:25:01 2017 -0400
Committer: Woonsan Ko <wo...@apache.org>
Committed: Sun Jul 2 01:25:01 2017 -0400

----------------------------------------------------------------------
 freemarker-spring/build.gradle                  |  40 ++++--
 .../spring/web/view/AbstractFreemarkerView.java |  37 ++----
 .../spring/web/view/FreemarkerView.java         | 129 ++-----------------
 .../spring/web/view/FreemarkerViewResolver.java |  90 ++++++++++++-
 .../spring/web/view/PageContextServlet.java     |  39 ++++++
 .../web/view/PageContextServletConfig.java      |  56 ++++++++
 .../freemarker/spring/web/view/EchoTag.java     |  55 ++++++++
 .../spring/web/view/FreemarkerViewTest.java     |  61 ++++++---
 .../src/test/resources/WEB-INF/echo.tld         |  46 +++++++
 .../src/test/resources/WEB-INF/web.xml          |  23 ++++
 10 files changed, 408 insertions(+), 168 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9083de1b/freemarker-spring/build.gradle
----------------------------------------------------------------------
diff --git a/freemarker-spring/build.gradle b/freemarker-spring/build.gradle
index 1865cc2..dce28d9 100644
--- a/freemarker-spring/build.gradle
+++ b/freemarker-spring/build.gradle
@@ -29,33 +29,57 @@ dependencies {
     compile project(":freemarker-core")
     compile project(":freemarker-servlet")
 
-    // TODO: what's difference between compileOnly and compile??
-    compile "org.apache.geronimo.specs:geronimo-servlet_3.0_spec:1.0"
-
+    def geronimoServletSpec3Version = "1.0"
     def springVersion = "4.3.9.RELEASE"
 
-    compile("org.springframework:spring-core:$springVersion") {
+    compileOnly "org.apache.geronimo.specs:geronimo-servlet_3.0_spec:${geronimoServletSpec3Version}"
+
+    compileOnly("org.springframework:spring-core:$springVersion") {
         exclude group: "commons-logging", module: "commons-logging"
     }
-    compile("org.springframework:spring-beans:$springVersion") {
+    compileOnly("org.springframework:spring-beans:$springVersion") {
         exclude group: "commons-logging", module: "commons-logging"
     }
-    compile("org.springframework:spring-context:$springVersion") {
+    compileOnly("org.springframework:spring-context:$springVersion") {
         exclude group: "commons-logging", module: "commons-logging"
     }
-    compile("org.springframework:spring-web:$springVersion") {
+    compileOnly("org.springframework:spring-web:$springVersion") {
         exclude group: "commons-logging", module: "commons-logging"
     }
-    compile("org.springframework:spring-webmvc:$springVersion") {
+    compileOnly("org.springframework:spring-webmvc:$springVersion") {
         exclude group: "commons-logging", module: "commons-logging"
     }
 
     // ------------------------------------------------------------------------
     // For tests
 
+    def taglibsStandardVersion = "1.2.1"
+
+    testCompile "org.apache.geronimo.specs:geronimo-servlet_3.0_spec:${geronimoServletSpec3Version}"
+
+    testCompile("org.springframework:spring-core:$springVersion") {
+        exclude group: "commons-logging", module: "commons-logging"
+    }
+    testCompile("org.springframework:spring-beans:$springVersion") {
+        exclude group: "commons-logging", module: "commons-logging"
+    }
+    testCompile("org.springframework:spring-context:$springVersion") {
+        exclude group: "commons-logging", module: "commons-logging"
+    }
+    testCompile("org.springframework:spring-web:$springVersion") {
+        exclude group: "commons-logging", module: "commons-logging"
+    }
+    testCompile("org.springframework:spring-webmvc:$springVersion") {
+        exclude group: "commons-logging", module: "commons-logging"
+    }
     testCompile("org.springframework:spring-test:$springVersion") {
         exclude group: "commons-logging", module: "commons-logging"
     }
+
+    testCompile "javax.servlet.jsp:jsp-api:2.1"
+    testCompile "org.apache.taglibs:taglibs-standard-spec:${taglibsStandardVersion}"
+    testCompile "org.apache.taglibs:taglibs-standard-impl:${taglibsStandardVersion}"
+
 }
 
 jar {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9083de1b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/AbstractFreemarkerView.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/AbstractFreemarkerView.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/AbstractFreemarkerView.java
index 595ada2..38c2e3e 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/AbstractFreemarkerView.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/AbstractFreemarkerView.java
@@ -32,13 +32,13 @@ import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.TemplateNotFoundException;
 import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
 import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
-import org.springframework.web.servlet.view.AbstractView;
+import org.springframework.web.servlet.view.AbstractTemplateView;
 
-public abstract class AbstractFreemarkerView extends AbstractView {
+public abstract class AbstractFreemarkerView extends AbstractTemplateView {
 
     private Configuration configuration;
+    private ObjectWrapperAndUnwrapper objectWrapper;
     private String name;
     private Locale locale;
     private Serializable customLookupCondition;
@@ -49,15 +49,17 @@ public abstract class AbstractFreemarkerView extends AbstractView {
     }
 
     public void setConfiguration(Configuration configuration) {
-        if (!(configuration.getObjectWrapper() instanceof ObjectWrapperAndUnwrapper)) {
-            throw new RuntimeException(AbstractFreemarkerView.class.getSimpleName() + " requires an ObjectWrapper that "
-                    + "implements " + ObjectWrapperAndUnwrapper.class.getName() + ", but this class doesn't do that: "
-                    + configuration.getObjectWrapper().getClass().getName());
-        }
-
         this.configuration = configuration;
     }
 
+    public ObjectWrapperAndUnwrapper getObjectWrapper() {
+        return objectWrapper;
+    }
+
+    public void setObjectWrapper(ObjectWrapperAndUnwrapper objectWrapper) {
+        this.objectWrapper = objectWrapper;
+    }
+
     public String getName() {
         return name;
     }
@@ -91,9 +93,9 @@ public abstract class AbstractFreemarkerView extends AbstractView {
     }
 
     @Override
-    protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
+    protected void renderMergedTemplateModel(Map<String, Object> model, HttpServletRequest request,
             HttpServletResponse response) throws Exception {
-        getTemplate().process(createModel(model, getObjectWrapperForModel(), request, response), response.getWriter());
+        getTemplate().process(createModel(model, getObjectWrapper(), request, response), response.getWriter());
     }
 
     protected Template getTemplate()
@@ -101,19 +103,6 @@ public abstract class AbstractFreemarkerView extends AbstractView {
         return getConfiguration().getTemplate(getName(), getLocale(), getCustomLookupCondition(), isIgnoreMissing());
     }
 
-    protected ObjectWrapperAndUnwrapper getObjectWrapperForModel() {
-        ObjectWrapperAndUnwrapper wrapper;
-
-        if (configuration.isObjectWrapperSet()) {
-            wrapper = (ObjectWrapperAndUnwrapper) configuration.getObjectWrapper();
-        } else {
-            // TODO: need to cache this?
-            wrapper = new DefaultObjectWrapper.Builder(configuration.getIncompatibleImprovements()).build();
-        }
-
-        return wrapper;
-    }
-
     protected abstract TemplateHashModel createModel(Map<String, Object> map,
             ObjectWrapperAndUnwrapper objectWrapperForModel, HttpServletRequest request, HttpServletResponse response);
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9083de1b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/FreemarkerView.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/FreemarkerView.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/FreemarkerView.java
index 64b3158..2210b98 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/FreemarkerView.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/FreemarkerView.java
@@ -18,17 +18,8 @@
  */
 package org.apache.freemarker.spring.web.view;
 
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Enumeration;
 import java.util.Map;
 
-import javax.servlet.GenericServlet;
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
@@ -42,38 +33,15 @@ import org.apache.freemarker.servlet.HttpRequestParametersHashModel;
 import org.apache.freemarker.servlet.HttpSessionHashModel;
 import org.apache.freemarker.servlet.ServletContextHashModel;
 import org.apache.freemarker.servlet.jsp.TaglibFactory;
-import org.apache.freemarker.servlet.jsp.TaglibFactoryBuilder;
 
 public class FreemarkerView extends AbstractFreemarkerView {
 
-    private volatile PageContextServlet pageContextServlet;
-
-    private volatile ServletContextHashModel servletContextModel;
-
-    private volatile TaglibFactory taglibFactory;
+    private PageContextServlet pageContextServlet;
+    private ServletContextHashModel servletContextModel;
+    private TaglibFactory taglibFactory;
 
     public PageContextServlet getPageContextServlet() {
-        PageContextServlet servlet = pageContextServlet;
-
-        if (servlet == null) {
-            synchronized (this) {
-                servlet = pageContextServlet;
-
-                if (servlet == null) {
-                    servlet = new PageContextServlet();
-
-                    try {
-                        servlet.init(new PageContextServletConfig(getServletContext(), getBeanName()));
-                    } catch (ServletException e) {
-                        // never happens...
-                    }
-
-                    pageContextServlet = servlet;
-                }
-            }
-        }
-
-        return servlet;
+        return pageContextServlet;
     }
 
     public void setPageContextServlet(PageContextServlet pageContextServlet) {
@@ -81,20 +49,7 @@ public class FreemarkerView extends AbstractFreemarkerView {
     }
 
     public ServletContextHashModel getServletContextModel() {
-        ServletContextHashModel contextModel = servletContextModel;
-
-        if (contextModel == null) {
-            synchronized (this) {
-                contextModel = servletContextModel;
-
-                if (contextModel == null) {
-                    contextModel = new ServletContextHashModel(getPageContextServlet(), getObjectWrapperForModel());
-                    servletContextModel = contextModel;
-                }
-            }
-        }
-
-        return contextModel;
+        return servletContextModel;
     }
 
     public void setServletContextModel(ServletContextHashModel servletContextModel) {
@@ -102,22 +57,7 @@ public class FreemarkerView extends AbstractFreemarkerView {
     }
 
     public TaglibFactory getTaglibFactory() {
-        TaglibFactory tlFactory = taglibFactory;
-
-        if (tlFactory == null) {
-            synchronized (this) {
-                tlFactory = taglibFactory;
-
-                if (tlFactory == null) {
-                    tlFactory = new TaglibFactoryBuilder(getServletContext(), getObjectWrapperForModel())
-                            .build();
-
-                    taglibFactory = tlFactory;
-                }
-            }
-        }
-
-        return tlFactory;
+        return taglibFactory;
     }
 
     public void setTaglibFactory(TaglibFactory taglibFactory) {
@@ -125,15 +65,15 @@ public class FreemarkerView extends AbstractFreemarkerView {
     }
 
     @Override
-    protected TemplateHashModel createModel(Map<String, Object> map, ObjectWrapperAndUnwrapper objectWrapperForModel,
+    protected TemplateHashModel createModel(Map<String, Object> map, ObjectWrapperAndUnwrapper objectWrapper,
             HttpServletRequest request, HttpServletResponse response) {
 
-        AllHttpScopesHashModel model = new AllHttpScopesHashModel(objectWrapperForModel, getServletContext(), request);
+        AllHttpScopesHashModel model = new AllHttpScopesHashModel(objectWrapper, getServletContext(), request);
 
         model.putUnlistedModel(FreemarkerServlet.KEY_APPLICATION, getServletContextModel());
 
         model.putUnlistedModel(FreemarkerServlet.KEY_SESSION,
-                getHttpSessionModel(objectWrapperForModel, request, response));
+                getHttpSessionModel(objectWrapper, request, response));
 
         HttpRequestHashModel requestModel = (HttpRequestHashModel) request
                 .getAttribute(FreemarkerServlet.ATTR_REQUEST_MODEL);
@@ -141,9 +81,9 @@ public class FreemarkerView extends AbstractFreemarkerView {
                 .getAttribute(FreemarkerServlet.ATTR_REQUEST_PARAMETERS_MODEL);
 
         if (requestModel == null || requestModel.getRequest() != request) {
-            requestModel = new HttpRequestHashModel(request, response, objectWrapperForModel);
+            requestModel = new HttpRequestHashModel(request, response, objectWrapper);
             request.setAttribute(FreemarkerServlet.ATTR_REQUEST_MODEL, requestModel);
-            requestParametersModel = new HttpRequestParametersHashModel(request, objectWrapperForModel);
+            requestParametersModel = new HttpRequestParametersHashModel(request, objectWrapper);
         }
 
         model.putUnlistedModel(FreemarkerServlet.KEY_REQUEST, requestModel);
@@ -175,51 +115,4 @@ public class FreemarkerView extends AbstractFreemarkerView {
         return sessionModel;
     }
 
-    /**
-     * Extending {@link GenericServlet} for {@link PageContext#getPage()} in JSP Tag Library support.
-     */
-    @SuppressWarnings("serial")
-    private static class PageContextServlet extends GenericServlet {
-
-        @Override
-        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
-            // Do nothing
-        }
-
-    }
-
-    /**
-     * {@link ServletConfig} for {@link PageContextServlet}.
-     */
-    private class PageContextServletConfig implements ServletConfig {
-
-        private ServletContext servletContext;
-        private String servletName;
-
-        public PageContextServletConfig(ServletContext servletContext, String servletName) {
-            this.servletContext = servletContext;
-            this.servletName = servletName;
-        }
-
-        @Override
-        public String getServletName() {
-            return servletName;
-        }
-
-        @Override
-        public ServletContext getServletContext() {
-            return servletContext;
-        }
-
-        @Override
-        public String getInitParameter(String name) {
-            return null;
-        }
-
-        @Override
-        public Enumeration<String> getInitParameterNames() {
-            return Collections.enumeration(Collections.<String> emptySet());
-        }
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9083de1b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/FreemarkerViewResolver.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/FreemarkerViewResolver.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/FreemarkerViewResolver.java
index 6d7ab40..ecacd42 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/FreemarkerViewResolver.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/FreemarkerViewResolver.java
@@ -18,9 +18,26 @@
  */
 package org.apache.freemarker.spring.web.view;
 
+import javax.servlet.ServletException;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
+import org.apache.freemarker.servlet.ServletContextHashModel;
+import org.apache.freemarker.servlet.jsp.TaglibFactory;
+import org.apache.freemarker.servlet.jsp.TaglibFactoryBuilder;
+import org.springframework.beans.factory.InitializingBean;
 import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
+import org.springframework.web.servlet.view.AbstractUrlBasedView;
+
+public class FreemarkerViewResolver extends AbstractTemplateViewResolver implements InitializingBean {
 
-public class FreemarkerViewResolver extends AbstractTemplateViewResolver {
+    private Configuration configuration;
+
+    private ObjectWrapperAndUnwrapper objectWrapper;
+    private PageContextServlet pageContextServlet;
+    private ServletContextHashModel servletContextModel;
+    private TaglibFactory taglibFactory;
 
     public FreemarkerViewResolver() {
         setViewClass(requiredViewClass());
@@ -32,9 +49,80 @@ public class FreemarkerViewResolver extends AbstractTemplateViewResolver {
         setSuffix(suffix);
     }
 
+    public Configuration getConfiguration() {
+        return configuration;
+    }
+
+    public void setConfiguration(Configuration configuration) {
+        this.configuration = configuration;
+    }
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        if (configuration == null) {
+            throw new IllegalStateException("Configuration is not set.");
+        }
+
+        if (objectWrapper == null) {
+            if (configuration.isObjectWrapperSet()) {
+                if (!(configuration.getObjectWrapper() instanceof ObjectWrapperAndUnwrapper)) {
+                    throw new RuntimeException(
+                            FreemarkerViewResolver.class.getSimpleName() + " requires an ObjectWrapper that "
+                                    + "implements " + ObjectWrapperAndUnwrapper.class.getName()
+                                    + ", but the Configuration's ObjectWrapper doesn't do that: "
+                                    + configuration.getObjectWrapper().getClass().getName());
+                }
+
+                objectWrapper = (ObjectWrapperAndUnwrapper) configuration.getObjectWrapper();
+            } else {
+                objectWrapper = new DefaultObjectWrapper.Builder(configuration.getIncompatibleImprovements()).build();
+            }
+        }
+
+        pageContextServlet = new PageContextServlet();
+
+        try {
+            pageContextServlet
+                    .init(new PageContextServletConfig(getServletContext(), FreemarkerViewResolver.class.getName()));
+        } catch (ServletException e) {
+            // never happens...
+        }
+
+        servletContextModel = new ServletContextHashModel(pageContextServlet, objectWrapper);
+
+        taglibFactory = new TaglibFactoryBuilder(getServletContext(), objectWrapper).build();
+    }
+
     @Override
     protected Class<?> requiredViewClass() {
         return FreemarkerView.class;
     }
 
+    @Override
+    protected AbstractUrlBasedView buildView(String viewName) throws Exception {
+        FreemarkerView view = (FreemarkerView) super.buildView(viewName);
+        view.setConfiguration(configuration);
+        view.setObjectWrapper(objectWrapper);
+        view.setPageContextServlet(pageContextServlet);
+        view.setServletContextModel(servletContextModel);
+        view.setTaglibFactory(taglibFactory);
+        return view;
+    }
+
+    protected ObjectWrapperAndUnwrapper getObjectWrapper() {
+        return objectWrapper;
+    }
+
+    protected PageContextServlet getPageContextServlet() {
+        return pageContextServlet;
+    }
+
+    protected ServletContextHashModel getServletContextModel() {
+        return servletContextModel;
+    }
+
+    protected TaglibFactory getTaglibFactory() {
+        return taglibFactory;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9083de1b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/PageContextServlet.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/PageContextServlet.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/PageContextServlet.java
new file mode 100644
index 0000000..94b586b
--- /dev/null
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/PageContextServlet.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.spring.web.view;
+
+import java.io.IOException;
+
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * <code>GenericServlet</code> to support {@link javax.servlet.jsp.PageContext#getPage()} when JspTaglibs is used.
+ */
+@SuppressWarnings("serial")
+class PageContextServlet extends GenericServlet {
+
+    @Override
+    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
+        // Do nothing
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9083de1b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/PageContextServletConfig.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/PageContextServletConfig.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/PageContextServletConfig.java
new file mode 100644
index 0000000..0d42ba4
--- /dev/null
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/PageContextServletConfig.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.spring.web.view;
+
+import java.util.Collections;
+import java.util.Enumeration;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+class PageContextServletConfig implements ServletConfig {
+
+    private ServletContext servletContext;
+    private String servletName;
+
+    public PageContextServletConfig(ServletContext servletContext, String servletName) {
+        this.servletContext = servletContext;
+        this.servletName = servletName;
+    }
+
+    @Override
+    public String getServletName() {
+        return servletName;
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        return servletContext;
+    }
+
+    @Override
+    public String getInitParameter(String name) {
+        return null;
+    }
+
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+        return Collections.enumeration(Collections.<String> emptySet());
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9083de1b/freemarker-spring/src/test/java/org/apache/freemarker/spring/web/view/EchoTag.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/web/view/EchoTag.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/web/view/EchoTag.java
new file mode 100644
index 0000000..155474f
--- /dev/null
+++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/web/view/EchoTag.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.spring.web.view;
+
+import java.io.IOException;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+/**
+ * Simple Example JSP Tag Library for unit test.
+ */
+@SuppressWarnings("serial")
+public class EchoTag extends TagSupport {
+
+    private String message;
+
+    public EchoTag() {
+        super();
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public int doEndTag() throws JspException {
+        try {
+            pageContext.getOut().print(message);
+        } catch (IOException e) {
+            throw new JspException("Message printing error.", e);
+        }
+        return EVAL_PAGE;
+    }
+
+    public void release() {
+        this.message = null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9083de1b/freemarker-spring/src/test/java/org/apache/freemarker/spring/web/view/FreemarkerViewTest.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/web/view/FreemarkerViewTest.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/web/view/FreemarkerViewTest.java
index 80fc2b3..4a6f4b1 100644
--- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/web/view/FreemarkerViewTest.java
+++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/web/view/FreemarkerViewTest.java
@@ -34,25 +34,41 @@ import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.mock.web.MockHttpSession;
 import org.springframework.mock.web.MockServletContext;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.GenericWebApplicationContext;
 
 public class FreemarkerViewTest {
 
     private ServletContext servletContext;
+    private GenericWebApplicationContext applicationContext;
 
     private StringTemplateLoader templateLoader;
-
     private Configuration configuration;
 
+    private FreemarkerViewResolver viewResolver;
+
     private AtomicLong visitorCount;
 
     @Before
     public void setUp() throws Exception {
         servletContext = new MockServletContext();
-        visitorCount = new AtomicLong();
-        servletContext.setAttribute("visitorCount", visitorCount);
+
+        applicationContext = new GenericWebApplicationContext(servletContext);
+        applicationContext.refresh();
+        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, applicationContext);
+
         templateLoader = new StringTemplateLoader();
         configuration = new Configuration.Builder(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS)
                 .templateLoader(templateLoader).build();
+
+        viewResolver = new FreemarkerViewResolver();
+        viewResolver.setServletContext(servletContext);
+        viewResolver.setApplicationContext(applicationContext);
+        viewResolver.setConfiguration(configuration);
+        viewResolver.afterPropertiesSet();
+
+        visitorCount = new AtomicLong();
+        servletContext.setAttribute("visitorCount", visitorCount);
     }
 
     @Test
@@ -63,10 +79,7 @@ public class FreemarkerViewTest {
 
         templateLoader.putTemplate("hello.ftl", "Hello, ${name!\"World\"}! Visit count: ${visitCount!0}");
 
-        FreemarkerView view = new FreemarkerView();
-        view.setServletContext(servletContext);
-        view.setConfiguration(configuration);
-        view.setName("hello.ftl");
+        FreemarkerView view = createFreemarkerView("hello.ftl");
 
         int visitCount = 0;
         Map<String, Object> model = new HashMap<String, Object>();
@@ -100,10 +113,7 @@ public class FreemarkerViewTest {
                         + "BTW, you're ${Application.visitorCount}th visitor. "
                         + "(token1: ${RequestParameters['token1']!})");
 
-        FreemarkerView view = new FreemarkerView();
-        view.setServletContext(servletContext);
-        view.setConfiguration(configuration);
-        view.setName("default-model.ftl");
+        FreemarkerView view = createFreemarkerView("default-model.ftl");
 
         Map<String, Object> model = new HashMap<String, Object>();
         model.put("name", "Dan");
@@ -121,16 +131,33 @@ public class FreemarkerViewTest {
         request.setContextPath("/mycontext");
         request.setServletPath("/myservlet");
 
-        // TODO: 
-        templateLoader.putTemplate("taglibs.ftl", "");
+        templateLoader.putTemplate("taglibs.ftl",
+                "<#assign e=JspTaglibs ['http://freemarker.org/jsp/example/echo'] >"
+                + "<#assign msg=\"Hello!\" />"
+                + "<@e.echo message=msg />");
 
-        FreemarkerView view = new FreemarkerView();
-        view.setServletContext(servletContext);
-        view.setConfiguration(configuration);
-        view.setName("taglibs.ftl");
+        FreemarkerView view = createFreemarkerView("taglibs.ftl");
 
         Map<String, Object> model = new HashMap<String, Object>();
         MockHttpServletResponse response = new MockHttpServletResponse();
         view.render(model, request, response);
+        assertEquals("Hello!", response.getContentAsString());
+    }
+
+    private FreemarkerView createFreemarkerView(final String name) {
+        FreemarkerView view = new FreemarkerView();
+
+        view.setServletContext(servletContext);
+        view.setApplicationContext(applicationContext);
+        view.setConfiguration(configuration);
+        view.setObjectWrapper(viewResolver.getObjectWrapper());
+
+        view.setPageContextServlet(viewResolver.getPageContextServlet());
+        view.setServletContextModel(viewResolver.getServletContextModel());
+        view.setTaglibFactory(viewResolver.getTaglibFactory());
+
+        view.setName(name);
+
+        return view;
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9083de1b/freemarker-spring/src/test/resources/WEB-INF/echo.tld
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/resources/WEB-INF/echo.tld b/freemarker-spring/src/test/resources/WEB-INF/echo.tld
new file mode 100644
index 0000000..aff4d98
--- /dev/null
+++ b/freemarker-spring/src/test/resources/WEB-INF/echo.tld
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<taglib xmlns="http://java.sun.com/xml/ns/javaee"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
+        version="2.1">
+
+    <description>Test Echo Tag Library</description>
+    <display-name>Test Echo Tag Library</display-name>
+    <tlib-version>1.2</tlib-version>
+    <short-name>e</short-name>
+    <uri>http://freemarker.org/jsp/example/echo</uri>
+
+    <tag>
+        <description>
+            Print the message.
+        </description>
+        <name>echo</name>
+        <tag-class>org.apache.freemarker.spring.web.view.EchoTag</tag-class>
+        <body-content>JSP</body-content>
+        <attribute>
+            <description>
+                Message string.
+            </description>
+            <name>message</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+    </tag>
+
+</taglib>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9083de1b/freemarker-spring/src/test/resources/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/resources/WEB-INF/web.xml b/freemarker-spring/src/test/resources/WEB-INF/web.xml
new file mode 100644
index 0000000..f091779
--- /dev/null
+++ b/freemarker-spring/src/test/resources/WEB-INF/web.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<web-app version="3.0">
+
+  <display-name>Test Web Application descriptor in Apache FreeMarker Spring Framework support module</display-name>
+  <description>Test Web Application descriptor in Apache FreeMarker Spring Framework support module</description>
+
+</web-app>