001 // Copyright Mar 18, 2006 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014 package org.apache.tapestry.services.impl;
015
016 import java.io.IOException;
017 import java.io.PrintWriter;
018 import java.util.ArrayList;
019 import java.util.Iterator;
020 import java.util.List;
021
022 import org.apache.hivemind.util.Defense;
023 import org.apache.tapestry.IComponent;
024 import org.apache.tapestry.IJSONRender;
025 import org.apache.tapestry.IMarkupWriter;
026 import org.apache.tapestry.IPage;
027 import org.apache.tapestry.IRender;
028 import org.apache.tapestry.IRequestCycle;
029 import org.apache.tapestry.engine.NullWriter;
030 import org.apache.tapestry.json.IJSONWriter;
031 import org.apache.tapestry.markup.MarkupWriterSource;
032 import org.apache.tapestry.services.RequestLocaleManager;
033 import org.apache.tapestry.services.ResponseBuilder;
034 import org.apache.tapestry.services.ServiceConstants;
035 import org.apache.tapestry.util.ContentType;
036 import org.apache.tapestry.web.WebResponse;
037
038 /**
039 * Class that implements JSON responses in tapestry.
040 *
041 * @see <a href="http://json.org">json.org</a>
042 * @author jkuhnert
043 */
044 public class JSONResponseBuilder implements ResponseBuilder
045 {
046 /** Writer that creates JSON output response. */
047 protected IJSONWriter _writer;
048 /** Passed in to bypass normal rendering. */
049 protected IMarkupWriter _nullWriter = NullWriter.getSharedInstance();
050
051 /** Parts that will be updated. */
052 protected List _parts = new ArrayList();
053
054 protected RequestLocaleManager _localeManager;
055
056 protected MarkupWriterSource _markupWriterSource;
057
058 protected WebResponse _webResponse;
059
060 private IRequestCycle _cycle;
061
062 /**
063 * Creates a new response builder with the required services it needs
064 * to render the response when {@link #renderResponse(IRequestCycle)} is called.
065 *
066 * @param localeManager
067 * Used to set the locale on the response.
068 * @param markupWriterSource
069 * Creates IJSONWriter instance to be used.
070 * @param webResponse
071 * Web response for output stream.
072 */
073 public JSONResponseBuilder(IRequestCycle cycle, RequestLocaleManager localeManager,
074 MarkupWriterSource markupWriterSource,
075 WebResponse webResponse)
076 {
077 Defense.notNull(cycle, "cycle");
078
079 _cycle = cycle;
080 _localeManager = localeManager;
081 _markupWriterSource = markupWriterSource;
082 _webResponse = webResponse;
083 }
084
085 /**
086 *
087 * {@inheritDoc}
088 */
089 public boolean isDynamic()
090 {
091 return Boolean.TRUE;
092 }
093
094 /**
095 * {@inheritDoc}
096 */
097 public void renderResponse(IRequestCycle cycle)
098 throws IOException
099 {
100 _localeManager.persistLocale();
101
102 IPage page = cycle.getPage();
103
104 ContentType contentType = page.getResponseContentType();
105
106 String encoding = contentType.getParameter(ENCODING_KEY);
107
108 if (encoding == null)
109 {
110 encoding = cycle.getEngine().getOutputEncoding();
111
112 contentType.setParameter(ENCODING_KEY, encoding);
113 }
114
115 PrintWriter printWriter = _webResponse.getPrintWriter(contentType);
116
117 _writer = _markupWriterSource.newJSONWriter(printWriter, contentType);
118
119 // render response
120
121 parseParameters(cycle);
122
123 cycle.renderPage(this);
124
125 _writer.close();
126 }
127
128 /**
129 * Grabs the incoming parameters needed for json responses, most notable the
130 * {@link ServiceConstants#UPDATE_PARTS} parameter.
131 *
132 * @param cycle
133 * The request cycle to parse from
134 */
135 protected void parseParameters(IRequestCycle cycle)
136 {
137 Object[] updateParts = cycle.getParameters(ServiceConstants.UPDATE_PARTS);
138
139 if (updateParts == null)
140 return;
141
142 for(int i = 0; i < updateParts.length; i++)
143 _parts.add(updateParts[i].toString());
144 }
145
146 /**
147 * {@inheritDoc}
148 */
149 public void render(IMarkupWriter writer, IRender render, IRequestCycle cycle)
150 {
151 if (IJSONRender.class.isInstance(render)
152 && IComponent.class.isInstance(render))
153 {
154 IJSONRender json = (IJSONRender) render;
155 IComponent component = (IComponent) render;
156
157 if (!_parts.contains(component.getId()))
158 {
159 render.render(_nullWriter, cycle);
160 return;
161 }
162
163 json.renderComponent(_writer, cycle);
164 }
165
166 render.render(_nullWriter, cycle);
167 }
168
169 /**
170 * {@inheritDoc}
171 */
172 public void updateComponent(String id)
173 {
174 if (!_parts.contains(id))
175 _parts.add(id);
176 }
177
178 /**
179 * Determines if the specified component is contained in the
180 * responses requested update parts.
181 * @param target
182 * The component to check for.
183 * @return True if the request should capture the components output.
184 */
185 public boolean contains(IComponent target)
186 {
187 if (target == null)
188 return false;
189
190 String id = getComponentId(target);
191
192 if (_parts.contains(id))
193 return true;
194
195 Iterator it = _cycle.renderStackIterator();
196 while (it.hasNext()) {
197
198 IComponent comp = (IComponent)it.next();
199 String compId = getComponentId(comp);
200
201 if (comp != target && _parts.contains(compId))
202 return true;
203 }
204
205 return false;
206 }
207
208 /**
209 * Gets the id of the specified component, choosing the "id" element
210 * binding over any other id.
211 * @param comp
212 * @return The id of the component.
213 */
214 String getComponentId(IComponent comp)
215 {
216 return comp.getClientId();
217 }
218
219 /**
220 * {@inheritDoc}
221 */
222 public IMarkupWriter getWriter()
223 {
224 return _nullWriter;
225 }
226
227 /**
228 * {@inheritDoc}
229 */
230 public IMarkupWriter getWriter(String id, String type)
231 {
232 return _nullWriter;
233 }
234
235 /**
236 * {@inheritDoc}
237 */
238 public boolean isBodyScriptAllowed(IComponent target)
239 {
240 return false;
241 }
242
243 /**
244 * {@inheritDoc}
245 */
246 public boolean isExternalScriptAllowed(IComponent target)
247 {
248 return false;
249 }
250
251 /**
252 * {@inheritDoc}
253 */
254 public boolean isInitializationScriptAllowed(IComponent target)
255 {
256 return false;
257 }
258
259 /**
260 * {@inheritDoc}
261 */
262 public boolean isImageInitializationAllowed(IComponent target)
263 {
264 return false;
265 }
266
267 /**
268 * {@inheritDoc}
269 */
270 public void beginBodyScript(IMarkupWriter writer, IRequestCycle cycle)
271 {
272 // does nothing
273 }
274
275 /**
276 * {@inheritDoc}
277 */
278 public void endBodyScript(IMarkupWriter writer, IRequestCycle cycle)
279 {
280 // does nothing
281 }
282
283 /**
284 * {@inheritDoc}
285 */
286 public void writeBodyScript(IMarkupWriter writer, String script, IRequestCycle cycle)
287 {
288 // does nothing
289 }
290
291 /**
292 * {@inheritDoc}
293 */
294 public void writeExternalScript(IMarkupWriter normalWriter, String url, IRequestCycle cycle)
295 {
296 // does nothing
297 }
298
299 /**
300 * {@inheritDoc}
301 */
302 public void writeImageInitializations(IMarkupWriter writer, String script, String preloadName, IRequestCycle cycle)
303 {
304 // does nothing
305 }
306
307 /**
308 * {@inheritDoc}
309 */
310 public void writeInitializationScript(IMarkupWriter writer, String script)
311 {
312 // does nothing
313 }
314 }