View Javadoc

1   package org.cateproject.view.ajax;
2   
3   /*
4    * Copyright 2004-2008 the original author or authors.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.Map;
22  
23  import javax.servlet.ServletContext;
24  import javax.servlet.ServletException;
25  import javax.servlet.http.HttpServletRequest;
26  import javax.servlet.http.HttpServletResponse;
27  
28  import org.apache.tiles.Attribute;
29  import org.apache.tiles.AttributeContext;
30  import org.apache.tiles.Definition;
31  import org.apache.tiles.Attribute.AttributeType;
32  import org.apache.tiles.context.ChainedTilesRequestContextFactory;
33  import org.apache.tiles.context.TilesRequestContext;
34  import org.apache.tiles.context.TilesRequestContextFactory;
35  import org.apache.tiles.definition.DefinitionsFactoryException;
36  import org.apache.tiles.impl.BasicTilesContainer;
37  import org.apache.tiles.servlet.context.ServletUtil;
38  import org.springframework.js.ajax.AjaxHandler;
39  import org.springframework.js.ajax.SpringJavascriptAjaxHandler;
40  import org.springframework.util.StringUtils;
41  import org.springframework.web.servlet.support.JstlUtils;
42  import org.springframework.web.servlet.support.RequestContext;
43  import org.springframework.web.servlet.view.tiles2.TilesView;
44  
45  /**
46   * Tiles view implementation that is able to handle partial rendering for Spring Javascript Ajax requests.
47   * 
48   * <p>
49   * This implementation uses the {@link SpringJavascriptAjaxHandler} by default to determine whether the current request
50   * is an Ajax request. On an Ajax request, a "fragments" parameter will be extracted from the request in order to
51   * determine which attributes to render from the current tiles view.
52   * </p>
53   * 
54   * @author Jeremy Grelle
55   * @author David Winterfeldt
56   */
57  public class AjaxTiles21View extends TilesView {
58  
59      private static final String FRAGMENTS_PARAM = "fragments";
60  
61      private TilesRequestContextFactory tilesRequestContextFactory;
62  
63      private AjaxHandler ajaxHandler = new SpringJavascriptAjaxHandler();
64  
65      @Override
66      public void afterPropertiesSet() throws Exception {
67  	super.afterPropertiesSet();
68  	if (tilesRequestContextFactory == null) {
69  	    tilesRequestContextFactory = new ChainedTilesRequestContextFactory();
70  	    tilesRequestContextFactory.init(new HashMap<String, String>());
71  	}
72      }
73  
74      public AjaxHandler getAjaxHandler() {
75  	return ajaxHandler;
76      }
77  
78      public void setAjaxHandler(AjaxHandler ajaxHandler) {
79  	this.ajaxHandler = ajaxHandler;
80      }
81  
82      protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response)
83  	    throws Exception {
84  
85  	ServletContext servletContext = getServletContext();
86  	if (ajaxHandler.isAjaxRequest(request, response)) {
87  
88  	    String[] attrNames = getRenderFragments(model, request, response);
89  	    if (attrNames.length == 0) {
90  		logger.warn("An Ajax request was detected, but no fragments were specified to be re-rendered.  "
91  			+ "Falling back to full page render.  This can cause unpredictable results when processing "
92  			+ "the ajax response on the client.");
93  		super.renderMergedOutputModel(model, request, response);
94  		return;
95  	    }
96  
97  	    BasicTilesContainer container = (BasicTilesContainer) ServletUtil.getCurrentContainer(request,
98  		    servletContext);
99  	    if (container == null) {
100 		throw new ServletException("Tiles container is not initialized. "
101 			+ "Have you added a TilesConfigurer to your web application context?");
102 	    }
103 
104 	    exposeModelAsRequestAttributes(model, request);
105 	    JstlUtils.exposeLocalizationContext(new RequestContext(request, servletContext));
106 
107 	    TilesRequestContext tilesRequestContext = tilesRequestContextFactory.createRequestContext(container
108 		    .getApplicationContext(), new Object[] { request, response });
109 	    Definition compositeDefinition = container.getDefinitionsFactory().getDefinition(getUrl(),
110 		    tilesRequestContext);
111 	    Map flattenedAttributeMap = new HashMap();
112 	    flattenAttributeMap(container, tilesRequestContext, flattenedAttributeMap, compositeDefinition, request,
113 		    response);
114 
115 	    // initialize the session before rendering any fragments. Otherwise views that require the session which has
116 	    // not otherwise been initialized will fail to render
117 	    request.getSession();
118 	    // set the content type prior to calling response.getWriter() otherwise subsequent calls to setContentType will
119 	    // have no effect, failing silently, and the response will come out as text/plain and more importantly
120 	    // the CharacterEncoding is not set properly and all of the international characters come out wrong because
121 	    // the charset is not set on the writer!
122 	    response.setContentType(getContentType());
123 	    /**
124 	     * makes this difficult to use with CatePageCachingFilter as it commits the response after the fragement is 
125 	     * rendered but prior to it being written to the client
126 	     */
127 	    // response.flushBuffer(); 
128 	    
129 	    if(logger.isInfoEnabled()) {
130 	        logger.info("Composite Definition " + compositeDefinition.getName() + " has " + compositeDefinition.getCascadedAttributeNames().size());
131 	    }
132 	    if(compositeDefinition.getCascadedAttributeNames() != null) { // The definition has cascaded attributes
133 	        for(String cascadedAttributeName : compositeDefinition.getCascadedAttributeNames()) {
134 	    	    if(logger.isInfoEnabled()) {
135 	    		   logger.info("Adding " + cascadedAttributeName + " to request");
136 	    	    }
137 	    	    request.setAttribute(cascadedAttributeName, compositeDefinition.getAttribute(cascadedAttributeName));
138 	        }
139 	    }
140 	   
141 	    for (int i = 0; i < attrNames.length; i++) {
142 		    Attribute attributeToRender = (Attribute) flattenedAttributeMap.get(attrNames[i]);
143 
144 		    if (attributeToRender == null) {
145 		        throw new ServletException("No tiles attribute with a name of '" + attrNames[i] + "' could be found for the current view: " + this);
146 		    } else {
147 		        container.render(attributeToRender, response.getWriter(), new Object[] { request, response });
148 		    }
149 	    }
150 	} else {
151 	    super.renderMergedOutputModel(model, request, response);
152 	}
153     }
154 
155     protected String[] getRenderFragments(Map model, HttpServletRequest request, HttpServletResponse response) {
156 	String attrName = request.getParameter(FRAGMENTS_PARAM);
157 	String[] renderFragments = StringUtils.commaDelimitedListToStringArray(attrName);
158 	return StringUtils.trimArrayElements(renderFragments);
159     }
160 
161     protected void flattenAttributeMap(BasicTilesContainer container, TilesRequestContext requestContext,
162 	    Map resultMap, Definition compositeDefinition, HttpServletRequest request, HttpServletResponse response)
163 	    throws Exception {
164 	if (compositeDefinition.getAttributes() != null && compositeDefinition.getAttributes().size() > 0) {
165 	    Iterator i = compositeDefinition.getAttributes().keySet().iterator();
166 	    while (i.hasNext()) {
167 		Object key = i.next();
168 		Attribute attr = (Attribute) compositeDefinition.getAttributes().get(key);
169 		AttributeType attrType = attr.getType() != null ? attr.getType() : detectType(container,
170 			requestContext, attr);
171 		if (AttributeType.DEFINITION.equals(attrType) || AttributeType.TEMPLATE.equals(attrType)) {
172 		    resultMap.put(key, attr);
173 		    if (AttributeType.DEFINITION.equals(attrType)) {
174 			Definition nestedDefinition = container.getDefinitionsFactory().getDefinition(
175 				attr.getValue().toString(), requestContext);
176 			if (nestedDefinition != null && nestedDefinition != compositeDefinition) {
177 			    flattenAttributeMap(container, requestContext, resultMap, nestedDefinition, request,
178 				    response);
179 			}
180 		    }
181 		}
182 	    }
183 	}
184 
185 	// Process dynamic attributes
186 	AttributeContext attributeContext = container.getAttributeContext(new Object[] { request, response });
187 
188 	for (Iterator i = attributeContext.getAttributeNames(); i.hasNext();) {
189 	    String key = (String) i.next();
190 	    Attribute attr = attributeContext.getAttribute(key);
191 	    resultMap.put(key, attr);
192 	}
193     }
194 
195     private AttributeType detectType(BasicTilesContainer container, TilesRequestContext requestContext, Attribute attr)
196 	    throws DefinitionsFactoryException {
197 	if (attr.getValue() instanceof String) {
198 	    if (container.getDefinitionsFactory().getDefinition(attr.getValue().toString(), requestContext) != null) {
199 		return AttributeType.DEFINITION;
200 	    } else if (attr.getValue().toString().startsWith("/")) {
201 		return AttributeType.TEMPLATE;
202 	    } else {
203 		return AttributeType.STRING;
204 	    }
205 	}
206 	return AttributeType.OBJECT;
207     }
208 }