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    }