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 org.apache.hivemind.Resource;
017    import org.apache.hivemind.util.Defense;
018    import org.apache.tapestry.*;
019    import org.apache.tapestry.asset.AssetFactory;
020    import org.apache.tapestry.engine.NullWriter;
021    import org.apache.tapestry.json.IJSONWriter;
022    import org.apache.tapestry.markup.MarkupWriterSource;
023    import org.apache.tapestry.services.RequestLocaleManager;
024    import org.apache.tapestry.services.ResponseBuilder;
025    import org.apache.tapestry.services.ServiceConstants;
026    import org.apache.tapestry.util.ContentType;
027    import org.apache.tapestry.util.PageRenderSupportImpl;
028    import org.apache.tapestry.web.WebRequest;
029    import org.apache.tapestry.web.WebResponse;
030    
031    import java.io.IOException;
032    import java.io.PrintWriter;
033    import java.util.ArrayList;
034    import java.util.Iterator;
035    import java.util.List;
036    
037    /**
038     * Class that implements JSON responses in tapestry.
039     *
040     * @see <a href="http://json.org">json.org</a>
041     * @author jkuhnert
042     */
043    public class JSONResponseBuilder implements ResponseBuilder
044    {
045        /** Writer that creates JSON output response. */
046        protected IJSONWriter _writer;
047        /** Passed in to bypass normal rendering. */
048        protected IMarkupWriter _nullWriter = NullWriter.getSharedInstance();
049    
050        /** Parts that will be updated. */
051        protected List _parts = new ArrayList();
052    
053        protected RequestLocaleManager _localeManager;
054        protected MarkupWriterSource _markupWriterSource;
055    
056        private WebResponse _response;
057    
058        private ContentType _contentType;
059    
060        private final AssetFactory _assetFactory;
061    
062        private final String _namespace;
063    
064        private PageRenderSupportImpl _prs;
065    
066        private IRequestCycle _cycle;
067    
068        /**
069         * Creates a new response builder with the required services it needs
070         * to render the response when {@link #renderResponse(IRequestCycle)} is called.
071         *
072         * @param localeManager
073         *          Used to set the locale on the response.
074         * @param markupWriterSource
075         *          Creates IJSONWriter instance to be used.
076         * @param webResponse
077         *          Web response for output stream.
078         */
079        public JSONResponseBuilder(IRequestCycle cycle, RequestLocaleManager localeManager,
080                                   MarkupWriterSource markupWriterSource,
081                                   WebResponse webResponse, WebRequest request, AssetFactory assetFactory, String namespace)
082        {
083            Defense.notNull(cycle, "cycle");
084    
085            _cycle = cycle;
086            _localeManager = localeManager;
087            _markupWriterSource = markupWriterSource;
088            _response = webResponse;
089    
090            // Used by PageRenderSupport
091    
092            _assetFactory = assetFactory;
093            _namespace = namespace;
094            
095            _prs = new PageRenderSupportImpl(_assetFactory, _namespace, this, cycle);
096        }
097    
098        /**
099         *
100         * {@inheritDoc}
101         */
102        public boolean isDynamic()
103        {
104            return true;
105        }
106    
107        /**
108         * {@inheritDoc}
109         */
110        public void renderResponse(IRequestCycle cycle)
111          throws IOException
112        {
113            _localeManager.persistLocale();
114    
115            IPage page = cycle.getPage();
116    
117            _contentType = page.getResponseContentType();
118    
119            String encoding = _contentType.getParameter(ENCODING_KEY);
120    
121            if (encoding == null)
122            {
123                encoding = cycle.getEngine().getOutputEncoding();
124    
125                _contentType.setParameter(ENCODING_KEY, encoding);
126            }
127    
128            if (_writer == null)
129            {
130                parseParameters(cycle);
131    
132                PrintWriter printWriter = _response.getPrintWriter(_contentType);
133    
134                _writer = _markupWriterSource.newJSONWriter(printWriter, _contentType);
135            }
136    
137            // render response
138    
139            TapestryUtils.storePageRenderSupport(cycle, _prs);
140    
141            cycle.renderPage(this);
142    
143            TapestryUtils.removePageRenderSupport(cycle);
144    
145            flush();
146    
147            _writer.close();
148        }
149    
150        public void flush()
151          throws IOException
152        {
153            // Important - causes any cookies stored to properly be written out before the
154            // rest of the response starts being written - see TAPESTRY-825
155    
156            _writer.flush();
157        }
158    
159        /**
160         * Grabs the incoming parameters needed for json responses, most notable the
161         * {@link ServiceConstants#UPDATE_PARTS} parameter.
162         *
163         * @param cycle
164         *            The request cycle to parse from
165         */
166        protected void parseParameters(IRequestCycle cycle)
167        {
168            Object[] updateParts = cycle.getParameters(ServiceConstants.UPDATE_PARTS);
169    
170            if (updateParts == null)
171                return;
172    
173            for(int i = 0; i < updateParts.length; i++)
174                _parts.add(updateParts[i].toString());
175        }
176    
177        /**
178         * {@inheritDoc}
179         */
180        public void render(IMarkupWriter writer, IRender render, IRequestCycle cycle)
181        {
182            if (IJSONRender.class.isInstance(render)
183                && IComponent.class.isInstance(render))
184            {
185                IJSONRender json = (IJSONRender) render;
186                IComponent component = (IComponent) render;
187    
188                if (!contains(component, component.peekClientId()))
189                {
190                    render.render(_nullWriter, cycle);
191                    return;
192                }
193    
194                json.renderComponent(_writer, cycle);
195            }
196    
197            render.render(_nullWriter, cycle);
198        }
199    
200        /**
201         * {@inheritDoc}
202         */
203        public void updateComponent(String id)
204        {
205            if (!_parts.contains(id))
206                _parts.add(id);
207        }
208    
209        /**
210         * Determines if the specified component is contained in the 
211         * responses requested update parts.
212         * @param target
213         *          The component to check for.
214         * @return True if the request should capture the components output.
215         */
216        public boolean contains(IComponent target)
217        {
218            if (target == null)
219                return false;
220    
221            String id = target.getClientId();
222    
223            return contains(target, id);
224        }
225    
226        boolean contains(IComponent target, String id)
227        {
228            if (_parts.contains(id))
229                return true;
230    
231            Iterator it = _cycle.renderStackIterator();
232            while (it.hasNext())
233            {
234                IComponent comp = (IComponent)it.next();
235                String compId = comp.getClientId();
236    
237                if (comp != target && _parts.contains(compId))
238                    return true;
239            }
240    
241            return false;
242        }
243    
244        /**
245         * {@inheritDoc}
246         */
247        public boolean explicitlyContains(IComponent target)
248        {
249            if (target == null)
250                return false;
251    
252            return _parts.contains(target.getId());
253        }
254    
255        /**
256         * {@inheritDoc}
257         */
258        public IMarkupWriter getWriter()
259        {
260            return _nullWriter;
261        }
262    
263        /**
264         * {@inheritDoc}
265         */
266        public IMarkupWriter getWriter(String id, String type)
267        {
268            return _nullWriter;
269        }
270    
271        /**
272         * {@inheritDoc}
273         */
274        public boolean isBodyScriptAllowed(IComponent target)
275        {
276            return false;
277        }
278    
279        /**
280         * {@inheritDoc}
281         */
282        public boolean isExternalScriptAllowed(IComponent target)
283        {
284            return false;
285        }
286    
287        /**
288         * {@inheritDoc}
289         */
290        public boolean isInitializationScriptAllowed(IComponent target)
291        {
292            return false;
293        }
294    
295        /**
296         * {@inheritDoc}
297         */
298        public boolean isImageInitializationAllowed(IComponent target)
299        {
300            return false;
301        }
302    
303        /**
304         * {@inheritDoc}
305         */
306        public String getPreloadedImageReference(IComponent target, IAsset source)
307        {
308            return _prs.getPreloadedImageReference(target, source);
309        }
310    
311        /**
312         * {@inheritDoc}
313         */
314        public String getPreloadedImageReference(IComponent target, String url)
315        {
316            return _prs.getPreloadedImageReference(target, url);
317        }
318    
319        /**
320         * {@inheritDoc}
321         */
322        public String getPreloadedImageReference(String url)
323        {
324            return _prs.getPreloadedImageReference(url);
325        }
326    
327        /**
328         * {@inheritDoc}
329         */
330        public void addBodyScript(IComponent target, String script)
331        {
332            _prs.addBodyScript(target, script);
333        }
334    
335        /**
336         * {@inheritDoc}
337         */
338        public void addBodyScript(String script)
339        {
340            _prs.addBodyScript(script);
341        }
342    
343        /**
344         * {@inheritDoc}
345         */
346        public void addExternalScript(IComponent target, Resource resource)
347        {
348            _prs.addExternalScript(target, resource);
349        }
350    
351        /**
352         * {@inheritDoc}
353         */
354        public void addExternalScript(Resource resource)
355        {
356            _prs.addExternalScript(resource);
357        }
358    
359        /**
360         * {@inheritDoc}
361         */
362        public void addInitializationScript(IComponent target, String script)
363        {
364            _prs.addInitializationScript(target, script);
365        }
366    
367        /**
368         * {@inheritDoc}
369         */
370        public void addInitializationScript(String script)
371        {
372            _prs.addInitializationScript(script);
373        }
374    
375        public void addScriptAfterInitialization(IComponent target, String script)
376        {
377            _prs.addScriptAfterInitialization(target, script);
378        }
379    
380        /**
381         * {@inheritDoc}
382         */
383        public String getUniqueString(String baseValue)
384        {
385            return _prs.getUniqueString(baseValue);
386        }
387    
388        /**
389         * {@inheritDoc}
390         */
391        public void writeBodyScript(IMarkupWriter writer, IRequestCycle cycle)
392        {
393            _prs.writeBodyScript(writer, cycle);
394        }
395    
396        /**
397         * {@inheritDoc}
398         */
399        public void writeInitializationScript(IMarkupWriter writer)
400        {
401            _prs.writeInitializationScript(writer);
402        }
403    
404        /**
405         * {@inheritDoc}
406         */
407        public void beginBodyScript(IMarkupWriter writer, IRequestCycle cycle)
408        {
409            // does nothing
410        }
411    
412        /**
413         * {@inheritDoc}
414         */
415        public void endBodyScript(IMarkupWriter writer, IRequestCycle cycle)
416        {
417            // does nothing
418        }
419    
420        /**
421         * {@inheritDoc}
422         */
423        public void writeBodyScript(IMarkupWriter writer, String script, IRequestCycle cycle)
424        {
425            // does nothing
426        }
427    
428        /**
429         * {@inheritDoc}
430         */
431        public void writeExternalScript(IMarkupWriter normalWriter, String url, IRequestCycle cycle)
432        {
433            // does nothing
434        }
435    
436        /**
437         * {@inheritDoc}
438         */
439        public void writeImageInitializations(IMarkupWriter writer, String script, String preloadName, IRequestCycle cycle)
440        {
441            // does nothing
442        }
443    
444        /**
445         * {@inheritDoc}
446         */
447        public void writeInitializationScript(IMarkupWriter writer, String script)
448        {
449            // does nothing
450        }
451    
452        /**
453         * This implementation does nothing.
454         * {@inheritDoc}
455         */
456        public void addStatusMessage(IMarkupWriter normalWriter, String category, String text)
457        {
458        }
459    }