From 3c1ef54f50e40310dbc9e651013db6065d16cfa0 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 6 Mar 2014 22:07:11 -0500 Subject: [PATCH] Add spring-js-tiles3 module Issue: SWF-1575 --- build.gradle | 80 +++++++- .../META-INF/gradle-plugins/merge.properties | 1 + ide.gradle | 104 ++++++++++ settings.gradle | 4 + .../js/ajax/tiles3/AjaxTilesView.java | 194 ++++++++++++++++++ .../js/ajax/tiles3/package-info.java | 21 ++ .../src/main/resources/.gitignore | 0 .../js/ajax/tiles3/AjaxTilesViewTests.java | 185 +++++++++++++++++ .../js/ajax/tiles3/tiles-definitions.xml | 21 ++ spring-js-tiles3/src/test/resources/log4j.xml | 48 +++++ .../js/ajax/tiles2/AjaxTilesView.java | 8 +- 11 files changed, 651 insertions(+), 15 deletions(-) create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/merge.properties create mode 100644 ide.gradle create mode 100644 spring-js-tiles3/src/main/java/org/springframework/js/ajax/tiles3/AjaxTilesView.java create mode 100644 spring-js-tiles3/src/main/java/org/springframework/js/ajax/tiles3/package-info.java create mode 100644 spring-js-tiles3/src/main/resources/.gitignore create mode 100644 spring-js-tiles3/src/test/java/org/springframework/js/ajax/tiles3/AjaxTilesViewTests.java create mode 100644 spring-js-tiles3/src/test/java/org/springframework/js/ajax/tiles3/tiles-definitions.xml create mode 100644 spring-js-tiles3/src/test/resources/log4j.xml diff --git a/build.gradle b/build.gradle index 16fa900f..06dc6d63 100644 --- a/build.gradle +++ b/build.gradle @@ -10,16 +10,18 @@ buildscript { } configure(allprojects) { - apply plugin: "eclipse" - apply plugin: "idea" - apply plugin: "javadocHotfix" + group = "org.springframework.webflow" + + apply plugin: "propdeps" + apply plugin: "java" + apply from: "${rootProject.projectDir}/ide.gradle" + apply plugin: "javadocHotfix" } -configure(subprojects) { subproject -> +configure(subprojects - project(":spring-build-src")) { subproject -> - apply plugin: "java" - apply plugin: "propdeps" + apply plugin: "merge" apply from: "${rootProject.projectDir}/publish-maven.gradle" sourceCompatibility=1.6 @@ -58,7 +60,7 @@ configure(subprojects.findAll {it.name != "spring-js-resources"}) { subproject - subproject.ext { springVersion = "4.0.1.RELEASE" springSecurityVersion = "3.2.0.RELEASE" - slf4jVersion = "1.6.1" + slf4jVersion = "1.7.5" log4jVersion = "1.2.15" } @@ -89,6 +91,18 @@ configure(subprojects.findAll {it.name != "spring-js-resources"}) { subproject - } } +project("spring-build-src") { + description = "Exposes gradle buildSrc for IDE support" + apply plugin: "groovy" + + dependencies { + compile gradleApi() + compile localGroovy() + } + + configurations.archives.artifacts.clear() +} + project("spring-binding") { description = "Spring Binding" @@ -133,10 +147,18 @@ project("spring-js") { compile("org.springframework:spring-webmvc:$springVersion") provided("javax.servlet:javax.servlet-api:3.0.1") optional("org.apache.tiles:tiles-api:2.2.2") - optional("org.apache.tiles:tiles-core:2.2.2") - optional("org.apache.tiles:tiles-jsp:2.2.2") - optional("org.apache.tiles:tiles-servlet:2.2.2") - testCompile("javax.servlet:jstl:1.2") + optional("org.apache.tiles:tiles-core:2.2.2") { + exclude group: "org.slf4j", module: "jcl-over-slf4j" + } + optional("org.apache.tiles:tiles-jsp:2.2.2") { + exclude group: "org.slf4j", module: "jcl-over-slf4j" + } + optional("org.apache.tiles:tiles-servlet:2.2.2") { + exclude group: "org.slf4j", module: "jcl-over-slf4j" + } + testCompile("javax.servlet:jstl:1.2") { + exclude group: "org.slf4j", module: "jcl-over-slf4j" + } testCompile("log4j:log4j:$log4jVersion") { exclude group: "javax.mail", module: "mail" exclude group: "javax.jms", module: "jms" @@ -149,6 +171,42 @@ project("spring-js") { } } +project("spring-js-tiles3") { + description = "Spring JS Tiles 3 Integration" + merge.into = project(":spring-js") + + dependencies { + compile("commons-logging:commons-logging:1.1.1") + compile("org.springframework:spring-core:$springVersion") + compile("org.springframework:spring-webmvc:$springVersion") + optional("javax.el:javax.el-api:2.2.4") + optional("javax.servlet:javax.servlet-api:3.0.1") + optional("javax.servlet.jsp:jsp-api:2.2") + optional("org.apache.tiles:tiles-request-api:1.0.1") + optional("org.apache.tiles:tiles-api:3.0.1") + optional("org.apache.tiles:tiles-core:3.0.1") { + exclude group: "org.slf4j", module: "jcl-over-slf4j" + } + optional("org.apache.tiles:tiles-servlet:3.0.1") { + exclude group: "org.slf4j", module: "jcl-over-slf4j" + } + optional("org.apache.tiles:tiles-jsp:3.0.1") { + exclude group: "org.slf4j", module: "jcl-over-slf4j" + } + optional("org.apache.tiles:tiles-el:3.0.1") { + exclude group: "org.slf4j", module: "jcl-over-slf4j" + } + optional("org.apache.tiles:tiles-extras:3.0.1") { + exclude group: "org.slf4j", module: "jcl-over-slf4j" + exclude group: "org.springframework", module: "spring-web" + } + testCompile("javax.servlet:jstl:1.2") + testCompile("org.slf4j:jcl-over-slf4j:$slf4jVersion") + testCompile("org.slf4j:slf4j-api:$slf4jVersion") + testCompile("org.springframework:spring-test:$springVersion") + } +} + project("spring-webflow") { description = "Spring Web Flow" diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/merge.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/merge.properties new file mode 100644 index 00000000..9cef8041 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/merge.properties @@ -0,0 +1 @@ +implementation-class=org.springframework.build.gradle.MergePlugin diff --git a/ide.gradle b/ide.gradle new file mode 100644 index 00000000..6ec13d44 --- /dev/null +++ b/ide.gradle @@ -0,0 +1,104 @@ +import org.gradle.plugins.ide.eclipse.model.ProjectDependency +import org.gradle.plugins.ide.eclipse.model.SourceFolder + + +apply plugin: "propdeps-eclipse" +apply plugin: "propdeps-idea" + +eclipse.jdt { + sourceCompatibility = 1.6 + targetCompatibility = 1.6 +} + +// Replace classpath entries with project dependencies (GRADLE-1116) +eclipse.classpath.file.whenMerged { classpath -> + def regexp = /.*?\/([^\/]+)\/build\/[^\/]+\/(?:main|test)/ // only match those that end in main or test (avoids removing necessary entries like build/classes/jaxb) + def projectOutputDependencies = classpath.entries.findAll { entry -> entry.path =~ regexp } + projectOutputDependencies.each { entry -> + def matcher = (entry.path =~ regexp) + if(matcher) { + def projectName = matcher[0][1] + def path = "/${projectName}" + if(!classpath.entries.find { e -> e instanceof ProjectDependency && e.path == path }) { + def dependency = new ProjectDependency(path, project(":${projectName}").path) + dependency.exported = true + classpath.entries.add(dependency) + } + classpath.entries.remove(entry) + } + } + classpath.entries.removeAll { entry -> (entry.path =~ /(?!.*?repack.*\.jar).*?\/([^\/]+)\/build\/libs\/[^\/]+\.jar/) } +} + + +// Use separate main/test outputs (prevents WTP from packaging test classes) +eclipse.classpath.defaultOutputDir = file(project.name+"/bin/eclipse") +eclipse.classpath.file.beforeMerged { classpath -> + classpath.entries.findAll{ it instanceof SourceFolder }.each { + if(it.output.startsWith("bin/")) { + it.output = null + } + } +} +eclipse.classpath.file.whenMerged { classpath -> + classpath.entries.findAll{ it instanceof SourceFolder }.each { + it.output = "bin/" + it.path.split("/")[1] + } +} + +// Allow projects to be used as WPT modules +eclipse.project.natures "org.eclipse.wst.common.project.facet.core.nature" + + +// Include project specific settings +task eclipseSettings(type: Copy) { + from rootProject.files( + "src/eclipse/org.eclipse.jdt.ui.prefs", + "src/eclipse/org.eclipse.wst.common.project.facet.core.xml") + into project.file('.settings/') + outputs.upToDateWhen { false } +} + +task eclipseWstComponent(type: Copy) { + from rootProject.files( + "src/eclipse/org.eclipse.wst.common.component") + into project.file('.settings/') + expand(deployname: project.name) + outputs.upToDateWhen { false } +} + +task eclipseJdtPrepare(type: Copy) { + from rootProject.file("src/eclipse/org.eclipse.jdt.core.prefs") + into project.file(".settings/") + outputs.upToDateWhen { false } +} + +task cleanEclipseJdtUi(type: Delete) { + delete project.file(".settings/org.eclipse.jdt.ui.prefs") + delete project.file("org.eclipse.jdt.core.prefs") + delete project.file(".settings/org.eclipse.wst.common.component") + delete project.file(".settings/org.eclipse.wst.common.project.facet.core.xml") +} + +tasks["eclipseJdt"].dependsOn(eclipseJdtPrepare) +tasks["cleanEclipse"].dependsOn(cleanEclipseJdtUi) +tasks["eclipse"].dependsOn(eclipseSettings, eclipseWstComponent) + + +// Filter 'build' folder + +eclipse.project.file.withXml { + def node = it.asNode() + + def filteredResources = node.get("filteredResources") + if(filteredResources) { + node.remove(filteredResources) + } + def filterNode = node.appendNode("filteredResources").appendNode("filter") + filterNode.appendNode("id", "1359048889071") + filterNode.appendNode("name", "") + filterNode.appendNode("type", "30") + def matcherNode = filterNode.appendNode("matcher") + matcherNode.appendNode("id", "org.eclipse.ui.ide.multiFilter") + matcherNode.appendNode("arguments", "1.0-projectRelativePath-matches-false-false-build") +} diff --git a/settings.gradle b/settings.gradle index 1273c39c..aca285a2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,6 +3,10 @@ rootProject.name = 'webflow' include 'spring-binding' include 'spring-faces' include 'spring-js' +include 'spring-js-tiles3' include 'spring-js-resources' include 'spring-webflow' +// Exposes gradle buildSrc +include "buildSrc" +rootProject.children.find{ it.name == "buildSrc" }.name = "spring-build-src" diff --git a/spring-js-tiles3/src/main/java/org/springframework/js/ajax/tiles3/AjaxTilesView.java b/spring-js-tiles3/src/main/java/org/springframework/js/ajax/tiles3/AjaxTilesView.java new file mode 100644 index 00000000..2216ab3f --- /dev/null +++ b/spring-js-tiles3/src/main/java/org/springframework/js/ajax/tiles3/AjaxTilesView.java @@ -0,0 +1,194 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.js.ajax.tiles3; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.tiles.Attribute; +import org.apache.tiles.AttributeContext; +import org.apache.tiles.Definition; +import org.apache.tiles.access.TilesAccess; +import org.apache.tiles.impl.BasicTilesContainer; +import org.apache.tiles.request.ApplicationContext; +import org.apache.tiles.request.Request; +import org.apache.tiles.request.servlet.ServletRequest; +import org.springframework.js.ajax.AjaxHandler; +import org.springframework.js.ajax.SpringJavascriptAjaxHandler; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.support.JstlUtils; +import org.springframework.web.servlet.support.RequestContext; +import org.springframework.web.servlet.view.tiles3.TilesView; + +/** + * Tiles 3 view implementation that is able to handle partial rendering for Spring + * Javascript Ajax requests. + * + *

This implementation uses the {@link SpringJavascriptAjaxHandler} by default + * to determine whether the current request is an Ajax request. On an Ajax request, + * a "fragments" parameter will be extracted from the request in order to + * determine which attributes to render from the current tiles view. + * + * @author Rossen Stoyanchev + * @since 2.4 + */ +public class AjaxTilesView extends TilesView { + + private static final String FRAGMENTS_PARAM = "fragments"; + + private AjaxHandler ajaxHandler = new SpringJavascriptAjaxHandler(); + + + public AjaxHandler getAjaxHandler() { + return this.ajaxHandler; + } + + public void setAjaxHandler(AjaxHandler ajaxHandler) { + this.ajaxHandler = ajaxHandler; + } + + + protected void renderMergedOutputModel(Map model, HttpServletRequest request, + HttpServletResponse response) throws Exception { + + ServletContext servletContext = getServletContext(); + if (ajaxHandler.isAjaxRequest(request, response)) { + + String[] fragmentsToRender = getRenderFragments(model, request, response); + if (fragmentsToRender.length == 0) { + logger.warn("An Ajax request was detected, but no fragments were specified to be re-rendered. " + + "Falling back to full page render. This can cause unpredictable results when processing " + + "the ajax response on the client."); + super.renderMergedOutputModel(model, request, response); + return; + } + + Request tilesRequest = createTilesRequest(request, response); + ApplicationContext tilesAppContext = tilesRequest.getApplicationContext(); + BasicTilesContainer container = (BasicTilesContainer) TilesAccess.getContainer(tilesAppContext); + + if (container == null) { + throw new ServletException("Tiles container is not initialized. " + + "Have you added a TilesConfigurer to your web application context?"); + } + + exposeModelAsRequestAttributes(model, request); + JstlUtils.exposeLocalizationContext(new RequestContext(request, servletContext)); + + Definition compositeDefinition = container.getDefinitionsFactory().getDefinition(getUrl(), tilesRequest); + + Map flattenedAttributeMap = new HashMap(); + flattenAttributeMap(container, tilesRequest, flattenedAttributeMap, compositeDefinition); + addRuntimeAttributes(container, tilesRequest, flattenedAttributeMap); + + if (fragmentsToRender.length > 1) { + tilesRequest.getContext("request").put(ServletRequest.FORCE_INCLUDE_ATTRIBUTE_NAME, true); + } + + for (String element : fragmentsToRender) { + Attribute attributeToRender = flattenedAttributeMap.get(element); + if (attributeToRender == null) { + throw new ServletException("No tiles attribute with a name of '" + element + + "' could be found for the current view: " + this); + } + container.startContext(tilesRequest).inheritCascadedAttributes(compositeDefinition); + container.render(attributeToRender, tilesRequest); + container.endContext(tilesRequest); + } + } else { + super.renderMergedOutputModel(model, request, response); + } + } + + protected String[] getRenderFragments(Map model, HttpServletRequest request, + HttpServletResponse response) { + + String attrName = request.getParameter(FRAGMENTS_PARAM); + String[] renderFragments = StringUtils.commaDelimitedListToStringArray(attrName); + return StringUtils.trimArrayElements(renderFragments); + } + + /** + * Iterate over all attributes in the given Tiles definition. Every attribute + * value that represents a template (i.e. start with "/") or is a nested + * definition is added to a Map. The method class itself recursively to traverse + * nested definitions. + * + * @param container the TilesContainer + * @param tilesRequest the Tiles Request + * @param resultMap the output Map where attributes of interest are added to. + * @param definition the definition to search for attributes of interest. + */ + protected void flattenAttributeMap(BasicTilesContainer container, Request tilesRequest, + Map resultMap, Definition definition) { + + Set attributeNames = new HashSet(); + if (definition.getLocalAttributeNames() != null) { + attributeNames.addAll(definition.getLocalAttributeNames()); + } + if (definition.getCascadedAttributeNames() != null) { + attributeNames.addAll(definition.getCascadedAttributeNames()); + } + for (String attributeName : attributeNames) { + Attribute attribute = definition.getAttribute(attributeName); + if (attribute.getValue() == null || !(attribute.getValue() instanceof String)) { + continue; + } + String value = attribute.getValue().toString(); + if (value.startsWith("/")) { + resultMap.put(attributeName, attribute); + } else if (container.isValidDefinition(value, tilesRequest)) { + resultMap.put(attributeName, attribute); + Definition nestedDefinition = container.getDefinitionsFactory().getDefinition(value, tilesRequest); + Assert.isTrue(nestedDefinition != definition, "Circular nested definition: " + value); + flattenAttributeMap(container, tilesRequest, resultMap, nestedDefinition); + } + } + } + + /** + * Iterate over dynamically added Tiles attributes (see "Runtime Composition" + * in the Tiles documentation) and add them to the output Map passed as input. + * + * @param container the Tiles container + * @param tilesRequest the Tiles request + * @param resultMap the output Map where attributes of interest are added to. + */ + protected void addRuntimeAttributes(BasicTilesContainer container, + Request tilesRequest, Map resultMap) { + + AttributeContext attributeContext = container.getAttributeContext(tilesRequest); + Set attributeNames = new HashSet(); + if (attributeContext.getLocalAttributeNames() != null) { + attributeNames.addAll(attributeContext.getLocalAttributeNames()); + } + if (attributeContext.getCascadedAttributeNames() != null) { + attributeNames.addAll(attributeContext.getCascadedAttributeNames()); + } + for (String name : attributeNames) { + Attribute attr = attributeContext.getAttribute(name); + resultMap.put(name, attr); + } + } +} diff --git a/spring-js-tiles3/src/main/java/org/springframework/js/ajax/tiles3/package-info.java b/spring-js-tiles3/src/main/java/org/springframework/js/ajax/tiles3/package-info.java new file mode 100644 index 00000000..517edd6c --- /dev/null +++ b/spring-js-tiles3/src/main/java/org/springframework/js/ajax/tiles3/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2014 the original author or authors. + * + * 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. + */ + +/** + * The Ajax integration between Spring.js and the Tiles3 layout system. + */ +package org.springframework.js.ajax.tiles3; + diff --git a/spring-js-tiles3/src/main/resources/.gitignore b/spring-js-tiles3/src/main/resources/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/spring-js-tiles3/src/test/java/org/springframework/js/ajax/tiles3/AjaxTilesViewTests.java b/spring-js-tiles3/src/test/java/org/springframework/js/ajax/tiles3/AjaxTilesViewTests.java new file mode 100644 index 00000000..881fec0f --- /dev/null +++ b/spring-js-tiles3/src/test/java/org/springframework/js/ajax/tiles3/AjaxTilesViewTests.java @@ -0,0 +1,185 @@ +package org.springframework.js.ajax.tiles3; + +import java.util.HashMap; +import java.util.Map; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import org.apache.tiles.Attribute; +import org.apache.tiles.AttributeContext; +import org.apache.tiles.Definition; +import org.apache.tiles.access.TilesAccess; +import org.apache.tiles.impl.BasicTilesContainer; +import org.apache.tiles.preparer.ViewPreparer; +import org.apache.tiles.request.ApplicationContext; +import org.apache.tiles.request.Request; +import org.apache.tiles.request.servlet.ServletRequest; +import org.apache.tiles.request.servlet.wildcard.WildcardServletApplicationContext; +import org.springframework.js.ajax.SpringJavascriptAjaxHandler; +import org.springframework.js.ajax.tiles3.AjaxTilesView; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.support.StaticWebApplicationContext; +import org.springframework.web.servlet.support.RequestContext; +import org.springframework.web.servlet.view.tiles3.TilesConfigurer; + + +public class AjaxTilesViewTests extends TestCase { + + private AjaxTilesView ajaxTilesView; + + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + private MockServletContext servletContext; + + + protected void setUp() throws Exception { + + servletContext = new MockServletContext("/org/springframework/js/ajax/tiles3/"); + request = new MockHttpServletRequest(servletContext); + response = new MockHttpServletResponse(); + + TilesConfigurer tc = new TilesConfigurer(); + tc.setDefinitions(new String[] { "tiles-definitions.xml" }); + tc.setValidateDefinitions(true); + tc.setServletContext(servletContext); + tc.setUseMutableTilesContainer(false); + tc.afterPropertiesSet(); + + ajaxTilesView = new AjaxTilesView(); + } + + private void setupStaticWebApplicationContext() { + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + wac.setServletContext(servletContext); + wac.refresh(); + request.setAttribute(RequestContext.WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac); + ajaxTilesView.setApplicationContext(wac); + } + + public void testFullPageRendering() throws Exception { + setupStaticWebApplicationContext(); + ajaxTilesView.setUrl("search"); + ajaxTilesView.afterPropertiesSet(); + ajaxTilesView.renderMergedOutputModel(new HashMap(), request, response); + assertEquals("/WEB-INF/layout.jsp", response.getForwardedUrl()); + } + + public void testAjaxRequestNoFragments() throws Exception { + setupStaticWebApplicationContext(); + request.addHeader("Accept", SpringJavascriptAjaxHandler.AJAX_ACCEPT_CONTENT_TYPE); + ajaxTilesView.setUrl("search"); + ajaxTilesView.afterPropertiesSet(); + ajaxTilesView.renderMergedOutputModel(new HashMap(), request, response); + assertEquals("/WEB-INF/layout.jsp", response.getForwardedUrl()); + } + + public void testRenderFragment_Template() throws Exception { + setupStaticWebApplicationContext(); + request.addHeader("Accept", SpringJavascriptAjaxHandler.AJAX_ACCEPT_CONTENT_TYPE); + request.addParameter("fragments", "searchResults"); + ajaxTilesView.setUrl("search"); + ajaxTilesView.afterPropertiesSet(); + ajaxTilesView.renderMergedOutputModel(new HashMap(), request, response); + assertEquals("/WEB-INF/searchResults.jsp", response.getForwardedUrl()); + } + + public void testRenderFragment_Definition() throws Exception { + setupStaticWebApplicationContext(); + request.addHeader("Accept", SpringJavascriptAjaxHandler.AJAX_ACCEPT_CONTENT_TYPE); + request.addParameter("fragments", "body"); + ajaxTilesView.setUrl("search"); + ajaxTilesView.afterPropertiesSet(); + ajaxTilesView.renderMergedOutputModel(new HashMap(), request, response); + assertEquals("/WEB-INF/search.jsp", response.getForwardedUrl()); + } + + public void testRenderFragment_CascadedAttribute() throws Exception { + setupStaticWebApplicationContext(); + request.addHeader("Accept", SpringJavascriptAjaxHandler.AJAX_ACCEPT_CONTENT_TYPE); + request.addParameter("fragments", "searchNavigation"); + ajaxTilesView.setUrl("search"); + ajaxTilesView.afterPropertiesSet(); + ajaxTilesView.renderMergedOutputModel(new HashMap(), request, response); + assertEquals("/WEB-INF/searchNavigation.jsp", response.getForwardedUrl()); + } + + public void testRenderFragment_InheritCascadedAttribute() throws Exception { + ApplicationContext tilesAppContext = new WildcardServletApplicationContext(servletContext); + Request tilesRequest = new ServletRequest(tilesAppContext, request, response); + BasicTilesContainer container = (BasicTilesContainer) TilesAccess.getContainer(tilesAppContext); + Definition definition = container.getDefinitionsFactory().getDefinition("search.body", tilesRequest); + definition.setPreparer(AttributeTestingPreparer.class.getName()); + setupStaticWebApplicationContext(); + request.addHeader("Accept", SpringJavascriptAjaxHandler.AJAX_ACCEPT_CONTENT_TYPE); + request.addParameter("fragments", "body"); + ajaxTilesView.setUrl("search"); + ajaxTilesView.afterPropertiesSet(); + ajaxTilesView.renderMergedOutputModel(new HashMap(), request, response); + assertTrue(AttributeTestingPreparer.invoked); + } + + public void testRenderFragment_DynamicAttribute() throws Exception { + ApplicationContext tilesAppContext = new WildcardServletApplicationContext(servletContext); + Request tilesRequest = new ServletRequest(tilesAppContext, request, response); + BasicTilesContainer container = (BasicTilesContainer) TilesAccess.getContainer(tilesAppContext); + AttributeContext attributeContext = container.startContext(tilesRequest); + attributeContext.putAttribute("body", new Attribute("/WEB-INF/dynamicTemplate.jsp")); + Map resultMap = new HashMap(); + ajaxTilesView.addRuntimeAttributes(container, tilesRequest, resultMap); + assertNotNull(resultMap.get("body")); + assertEquals("/WEB-INF/dynamicTemplate.jsp", resultMap.get("body").toString()); + container.endContext(tilesRequest); + } + + public void testRenderFragment_Multiple() throws Exception { + setupStaticWebApplicationContext(); + request.addHeader("Accept", SpringJavascriptAjaxHandler.AJAX_ACCEPT_CONTENT_TYPE); + request.addParameter("fragments", "body,searchNavigation"); + ajaxTilesView.setUrl("search"); + ajaxTilesView.afterPropertiesSet(); + ajaxTilesView.renderMergedOutputModel(new HashMap(), request, response); + assertTrue("Multiple fragments should result in include, not forward", response.getIncludedUrls().size() == 2); + assertEquals("/WEB-INF/search.jsp", response.getIncludedUrls().get(0)); + assertEquals("/WEB-INF/searchNavigation.jsp", response.getIncludedUrls().get(1)); + } + + public void testFlattenAttributeMap() throws Exception { + ApplicationContext tilesAppContext = new WildcardServletApplicationContext(servletContext); + Request tilesRequest = new ServletRequest(tilesAppContext, request, response); + BasicTilesContainer container = (BasicTilesContainer) TilesAccess.getContainer(tilesAppContext); + Definition compositeDefinition = container.getDefinitionsFactory().getDefinition("search", tilesRequest); + Map resultMap = new HashMap(); + ajaxTilesView.flattenAttributeMap(container, tilesRequest, resultMap, compositeDefinition); + assertNotNull(resultMap.get("body")); + assertNotNull(resultMap.get("searchForm")); + assertEquals("/WEB-INF/searchForm.jsp", resultMap.get("searchForm").toString()); + assertNotNull(resultMap.get("searchResults")); + } + + public void testGetRenderFragments() throws Exception { + Map model = new HashMap(); + request.setParameter("fragments", "f1,f2, f3"); + String[] fragments = ajaxTilesView.getRenderFragments(model, request, response); + assertEquals("f1", fragments[0]); + assertEquals("f2", fragments[1]); + assertEquals("f3", fragments[2]); + } + + + public static class AttributeTestingPreparer implements ViewPreparer { + + public static boolean invoked; + + public void execute(Request tilesContext, AttributeContext attributeContext) { + invoked = true; + Assert.assertTrue(attributeContext.getAttribute("searchNavigation") != null); + } + + } + +} diff --git a/spring-js-tiles3/src/test/java/org/springframework/js/ajax/tiles3/tiles-definitions.xml b/spring-js-tiles3/src/test/java/org/springframework/js/ajax/tiles3/tiles-definitions.xml new file mode 100644 index 00000000..10c11de6 --- /dev/null +++ b/spring-js-tiles3/src/test/java/org/springframework/js/ajax/tiles3/tiles-definitions.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-js-tiles3/src/test/resources/log4j.xml b/spring-js-tiles3/src/test/resources/log4j.xml new file mode 100644 index 00000000..b33a7753 --- /dev/null +++ b/spring-js-tiles3/src/test/resources/log4j.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-js/src/main/java/org/springframework/js/ajax/tiles2/AjaxTilesView.java b/spring-js/src/main/java/org/springframework/js/ajax/tiles2/AjaxTilesView.java index e1754dd9..0edda852 100644 --- a/spring-js/src/main/java/org/springframework/js/ajax/tiles2/AjaxTilesView.java +++ b/spring-js/src/main/java/org/springframework/js/ajax/tiles2/AjaxTilesView.java @@ -44,13 +44,13 @@ import org.springframework.web.servlet.view.tiles2.TilesView; /** * Tiles view implementation that is able to handle partial rendering for Spring Javascript Ajax requests. - * + * *

* This implementation uses the {@link SpringJavascriptAjaxHandler} by default to determine whether the current request * is an Ajax request. On an Ajax request, a "fragments" parameter will be extracted from the request in order to * determine which attributes to render from the current tiles view. *

- * + * * @author Jeremy Grelle * @author David Winterfeldt */ @@ -145,7 +145,7 @@ public class AjaxTilesView extends TilesView { * start with "/") or is a nested definition is added to a Map. The method class itself recursively to traverse * nested definitions. *

- * + * * @param container the TilesContainer * @param requestContext the TilesRequestContext * @param resultMap the output Map where attributes of interest are added to. @@ -180,7 +180,7 @@ public class AjaxTilesView extends TilesView { * Iterate over dynamically added Tiles attributes (see "Runtime Composition" in the Tiles documentation) and add * them to the output Map passed as input. *

- * + * * @param container the Tiles container * @param resultMap the output Map where attributes of interest are added to. * @param request the Servlet request