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        
096        /**
097         * 
098         * {@inheritDoc}
099         */
100        public boolean isDynamic()
101        {
102            return true;
103        }
104        
105        /**
106         * {@inheritDoc}
107         */
108        public void renderResponse(IRequestCycle cycle)
109        throws IOException
110        {
111            _localeManager.persistLocale();
112            
113            IPage page = cycle.getPage();
114            
115            _contentType = page.getResponseContentType();
116            
117            String encoding = _contentType.getParameter(ENCODING_KEY);
118            
119            if (encoding == null)
120            {
121                encoding = cycle.getEngine().getOutputEncoding();
122                
123                _contentType.setParameter(ENCODING_KEY, encoding);
124            }
125            
126            if (_writer == null) {
127                
128                parseParameters(cycle);
129    
130                PrintWriter printWriter = _response.getPrintWriter(_contentType);
131                
132                _writer = _markupWriterSource.newJSONWriter(printWriter, _contentType);
133            }
134            
135            // render response
136            
137            _prs = new PageRenderSupportImpl(_assetFactory, _namespace, cycle.getPage().getLocation(), this);
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        /**
376         * {@inheritDoc}
377         */
378        public String getUniqueString(String baseValue)
379        {
380            return _prs.getUniqueString(baseValue);
381        }
382        
383        /**
384         * {@inheritDoc}
385         */
386        public void writeBodyScript(IMarkupWriter writer, IRequestCycle cycle)
387        {
388            _prs.writeBodyScript(writer, cycle);
389        }
390        
391        /**
392         * {@inheritDoc}
393         */
394        public void writeInitializationScript(IMarkupWriter writer)
395        {
396            _prs.writeInitializationScript(writer);
397        }
398        
399        /** 
400         * {@inheritDoc}
401         */
402        public void beginBodyScript(IMarkupWriter writer, IRequestCycle cycle)
403        {
404            // does nothing
405        }
406    
407        /** 
408         * {@inheritDoc}
409         */
410        public void endBodyScript(IMarkupWriter writer, IRequestCycle cycle)
411        {
412            // does nothing
413        }
414    
415        /** 
416         * {@inheritDoc}
417         */
418        public void writeBodyScript(IMarkupWriter writer, String script, IRequestCycle cycle)
419        {
420            // does nothing
421        }
422    
423        /** 
424         * {@inheritDoc}
425         */
426        public void writeExternalScript(IMarkupWriter normalWriter, String url, IRequestCycle cycle)
427        {
428            // does nothing
429        }
430    
431        /** 
432         * {@inheritDoc}
433         */
434        public void writeImageInitializations(IMarkupWriter writer, String script, String preloadName, IRequestCycle cycle)
435        {
436            // does nothing
437        }
438    
439        /** 
440         * {@inheritDoc}
441         */
442        public void writeInitializationScript(IMarkupWriter writer, String script)
443        {
444            // does nothing
445        }
446    
447        /**
448         * This implementation does nothing.
449         * {@inheritDoc}
450         */
451        public void addStatusMessage(IMarkupWriter normalWriter, String category, String text)
452        {
453        }
454    }