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