001    package org.apache.tapestry.services.impl;
002    
003    import org.apache.hivemind.Resource;
004    import org.apache.hivemind.util.Defense;
005    import org.apache.tapestry.*;
006    import org.apache.tapestry.asset.AssetFactory;
007    import org.apache.tapestry.engine.NullWriter;
008    import org.apache.tapestry.markup.MarkupWriterSource;
009    import org.apache.tapestry.markup.NestedMarkupWriterImpl;
010    import org.apache.tapestry.services.RequestLocaleManager;
011    import org.apache.tapestry.services.ResponseBuilder;
012    import org.apache.tapestry.services.ServiceConstants;
013    import org.apache.tapestry.util.ContentType;
014    import org.apache.tapestry.util.PageRenderSupportImpl;
015    import org.apache.tapestry.web.WebResponse;
016    
017    import java.io.IOException;
018    import java.io.PrintWriter;
019    import java.util.*;
020    
021    /**
022     * Implementation of response builder for prototype client side library initiated XHR requests.
023     *
024     */
025    public class PrototypeResponseBuilder implements ResponseBuilder {
026    
027        public static final String CONTENT_TYPE = "text/html";
028    
029        private final AssetFactory _assetFactory;
030    
031        private final String _namespace;
032    
033        private PageRenderSupportImpl _prs;
034    
035        // used to create IMarkupWriter
036        private RequestLocaleManager _localeManager;
037        private MarkupWriterSource _markupWriterSource;
038        private WebResponse _response;
039    
040        // our response writer
041        private IMarkupWriter _writer;
042    
043        // Parts that will be updated.
044        private List _parts = new ArrayList();
045    
046        // Map of specialized writers, like scripts
047    
048        private Map _writers = new HashMap();
049        private IRequestCycle _cycle;
050    
051        /**
052         * Used for unit testing only.
053         *
054         * @param cycle Request.
055         * @param writer Markup writer.
056         * @param parts Update parts list.
057         */
058        public PrototypeResponseBuilder(IRequestCycle cycle, IMarkupWriter writer, List parts)
059        {
060            _cycle = cycle;
061            _writer = writer;
062    
063            if (parts != null)
064                _parts.addAll(parts);
065    
066            _assetFactory = null;
067            _namespace = null;
068        }
069    
070        /**
071         * Creates a new response builder with the required services it needs
072         * to render the response when {@link #renderResponse(IRequestCycle)} is called.
073         *
074         * @param cycle
075         *          Associated request.
076         * @param localeManager
077         *          Locale manager to use for response.
078         * @param markupWriterSource
079         *          Creates necessary {@link IMarkupWriter} instances.
080         * @param webResponse
081         *          The http response.
082         * @param assetFactory
083         *          Asset manager for script / other resource inclusion.
084         * @param namespace
085         *          Javascript namespace value - used in portlets.
086         */
087        public PrototypeResponseBuilder(IRequestCycle cycle,
088                                        RequestLocaleManager localeManager,
089                                        MarkupWriterSource markupWriterSource,
090                                        WebResponse webResponse,
091                                        AssetFactory assetFactory, String namespace)
092        {
093            Defense.notNull(cycle, "cycle");
094            Defense.notNull(assetFactory, "assetService");
095    
096            _cycle = cycle;
097            _localeManager = localeManager;
098            _markupWriterSource = markupWriterSource;
099            _response = webResponse;
100    
101            // Used by PageRenderSupport
102    
103            _assetFactory = assetFactory;
104            _namespace = namespace;
105            
106            _prs = new PageRenderSupportImpl(_assetFactory, _namespace, this, cycle);
107        }
108    
109        /**
110         *
111         * {@inheritDoc}
112         */
113        public boolean isDynamic()
114        {
115            return true;
116        }
117    
118        /**
119         * {@inheritDoc}
120         */
121        public void renderResponse(IRequestCycle cycle)
122          throws IOException
123        {
124            _localeManager.persistLocale();
125    
126            ContentType contentType = new ContentType(CONTENT_TYPE + ";charset=" + cycle.getInfrastructure().getOutputEncoding());
127    
128            String encoding = contentType.getParameter(ENCODING_KEY);
129    
130            if (encoding == null)
131            {
132                encoding = cycle.getEngine().getOutputEncoding();
133    
134                contentType.setParameter(ENCODING_KEY, encoding);
135            }
136    
137            if (_writer == null)
138            {
139                parseParameters(cycle);
140    
141                PrintWriter printWriter = _response.getPrintWriter(contentType);
142    
143                _writer = _markupWriterSource.newMarkupWriter(printWriter, contentType);
144            }
145    
146            // render response
147    
148            TapestryUtils.storePageRenderSupport(cycle, _prs);
149    
150            cycle.renderPage(this);
151    
152            TapestryUtils.removePageRenderSupport(cycle);
153    
154            endResponse();
155    
156            _writer.close();
157        }
158    
159        public void flush()
160          throws IOException
161        {
162            _writer.flush();
163        }
164    
165        /**
166         * {@inheritDoc}
167         */
168        public void updateComponent(String id)
169        {
170            if (!_parts.contains(id))
171                _parts.add(id);
172        }
173    
174        /**
175         * {@inheritDoc}
176         */
177        public IMarkupWriter getWriter()
178        {
179            return _writer;
180        }
181    
182        void setWriter(IMarkupWriter writer)
183        {
184            _writer = writer;
185        }
186    
187        /**
188         * {@inheritDoc}
189         */
190        public boolean isBodyScriptAllowed(IComponent target)
191        {
192            if (target != null
193                && IPage.class.isInstance(target)
194                || (IForm.class.isInstance(target)
195                    && ((IForm)target).isFormFieldUpdating()))
196                return true;
197    
198            return contains(target);
199        }
200    
201        /**
202         * {@inheritDoc}
203         */
204        public boolean isExternalScriptAllowed(IComponent target)
205        {
206            if (target != null
207                && IPage.class.isInstance(target)
208                || (IForm.class.isInstance(target)
209                    && ((IForm)target).isFormFieldUpdating()))
210                return true;
211    
212            return contains(target);
213        }
214    
215        /**
216         * {@inheritDoc}
217         */
218        public boolean isInitializationScriptAllowed(IComponent target)
219        {
220            if (target != null
221                && IPage.class.isInstance(target)
222                || (IForm.class.isInstance(target)
223                    && ((IForm)target).isFormFieldUpdating()))
224                return true;
225    
226            return contains(target);
227        }
228    
229        /**
230         * {@inheritDoc}
231         */
232        public boolean isImageInitializationAllowed(IComponent target)
233        {
234            if (target != null
235                && IPage.class.isInstance(target)
236                || (IForm.class.isInstance(target)
237                    && ((IForm)target).isFormFieldUpdating()))
238                return true;
239    
240            return contains(target);
241        }
242    
243        /**
244         * {@inheritDoc}
245         */
246        public String getPreloadedImageReference(IComponent target, IAsset source)
247        {
248            return _prs.getPreloadedImageReference(target, source);
249        }
250    
251        /**
252         * {@inheritDoc}
253         */
254        public String getPreloadedImageReference(IComponent target, String url)
255        {
256            return _prs.getPreloadedImageReference(target, url);
257        }
258    
259        /**
260         * {@inheritDoc}
261         */
262        public String getPreloadedImageReference(String url)
263        {
264            return _prs.getPreloadedImageReference(url);
265        }
266    
267        /**
268         * {@inheritDoc}
269         */
270        public void addBodyScript(IComponent target, String script)
271        {
272            _prs.addBodyScript(target, script);
273        }
274    
275        /**
276         * {@inheritDoc}
277         */
278        public void addBodyScript(String script)
279        {
280            _prs.addBodyScript(script);
281        }
282    
283        /**
284         * {@inheritDoc}
285         */
286        public void addExternalScript(IComponent target, Resource resource)
287        {
288            _prs.addExternalScript(target, resource);
289        }
290    
291        /**
292         * {@inheritDoc}
293         */
294        public void addExternalScript(Resource resource)
295        {
296            _prs.addExternalScript(resource);
297        }
298    
299        /**
300         * {@inheritDoc}
301         */
302        public void addInitializationScript(IComponent target, String script)
303        {
304            _prs.addInitializationScript(target, script);
305        }
306    
307        /**
308         * {@inheritDoc}
309         */
310        public void addInitializationScript(String script)
311        {
312            _prs.addInitializationScript(script);
313        }
314    
315        public void addScriptAfterInitialization(IComponent target, String script)
316        {
317            _prs.addScriptAfterInitialization(target, script);
318        }
319    
320        /**
321         * {@inheritDoc}
322         */
323        public String getUniqueString(String baseValue)
324        {
325            return _prs.getUniqueString(baseValue);
326        }
327    
328        /**
329         * {@inheritDoc}
330         */
331        public void writeBodyScript(IMarkupWriter writer, IRequestCycle cycle)
332        {
333            _prs.writeBodyScript(writer, cycle);
334        }
335    
336        /**
337         * {@inheritDoc}
338         */
339        public void writeInitializationScript(IMarkupWriter writer)
340        {
341            _prs.writeInitializationScript(writer);
342        }
343    
344        /**
345         * {@inheritDoc}
346         */
347        public void beginBodyScript(IMarkupWriter normalWriter, IRequestCycle cycle)
348        {
349            _writer.begin("script");
350            _writer.printRaw("\n//<![CDATA[\n");
351        }
352    
353        /**
354         * {@inheritDoc}
355         */
356        public void endBodyScript(IMarkupWriter normalWriter, IRequestCycle cycle)
357        {
358            _writer.printRaw("\n//]]>\n");
359            _writer.end();
360        }
361    
362        /**
363         * {@inheritDoc}
364         */
365        public void writeBodyScript(IMarkupWriter normalWriter, String script, IRequestCycle cycle)
366        {
367            _writer.printRaw(script);
368        }
369    
370        /**
371         * {@inheritDoc}
372         */
373        public void writeExternalScript(IMarkupWriter normalWriter, String url, IRequestCycle cycle)
374        {
375            _writer.begin("script");
376            _writer.attribute("type", "text/javascript");
377            _writer.attribute("src", url);
378            _writer.end();
379        }
380    
381        /**
382         * {@inheritDoc}
383         */
384        public void writeImageInitializations(IMarkupWriter normalWriter, String script, String preloadName, IRequestCycle cycle)
385        {
386        }
387    
388        /**
389         * {@inheritDoc}
390         */
391        public void writeInitializationScript(IMarkupWriter normalWriter, String script)
392        {
393            _writer.begin("script");
394    
395            // return is in XML so must escape any potentially non-xml compliant content
396            _writer.printRaw("\n//<![CDATA[\n");
397            _writer.printRaw(script);
398            _writer.printRaw("\n//]]>\n");
399            _writer.end();
400        }
401    
402        public void addStatus(IMarkupWriter normalWriter, String text)
403        {
404            throw new UnsupportedOperationException("Can't return a status response with prototype based requests.");
405        }
406    
407        public void addStatusMessage(IMarkupWriter normalWriter, String category, String text)
408        {
409            throw new UnsupportedOperationException("Can't return a status response with prototype based requests.");
410        }
411    
412        /**
413         * {@inheritDoc}
414         */
415        public void render(IMarkupWriter writer, IRender render, IRequestCycle cycle)
416        {
417            // must be a valid writer already
418    
419            if (NestedMarkupWriterImpl.class.isInstance(writer))
420            {
421                render.render(writer, cycle);
422                return;
423            }
424    
425            if (IComponent.class.isInstance(render)
426                && contains((IComponent)render, ((IComponent)render).peekClientId()))
427            {
428                render.render(getComponentWriter( ((IComponent)render).peekClientId() ), cycle);
429                return;
430            }
431    
432            // Nothing else found, throw out response
433    
434            render.render(NullWriter.getSharedInstance(), cycle);
435        }
436    
437        IMarkupWriter getComponentWriter(String id)
438        {
439            return getWriter(id, ELEMENT_TYPE);
440        }
441    
442        /**
443         *
444         * {@inheritDoc}
445         */
446        public IMarkupWriter getWriter(String id, String type)
447        {
448            Defense.notNull(id, "id can't be null");
449    
450            IMarkupWriter w = (IMarkupWriter)_writers.get(id);
451            if (w != null)
452                return w;
453    
454            IMarkupWriter nestedWriter = _writer.getNestedWriter();
455            _writers.put(id, nestedWriter);
456    
457            return nestedWriter;
458        }
459    
460        void beginResponse()
461        {
462        }
463    
464        /**
465         * Invoked to clear out tempoary partial writer buffers before rendering exception
466         * page.
467         */
468        void clearPartialWriters()
469        {
470            _writers.clear();
471        }
472    
473        /**
474         * Called after the entire response has been captured. Causes
475         * the writer buffer output captured to be segmented and written
476         * out to the right response elements for the client libraries to parse.
477         */
478        void endResponse()
479        {
480            Iterator keys = _writers.keySet().iterator();
481    
482            while (keys.hasNext())
483            {
484                String key = (String)keys.next();
485                NestedMarkupWriter nw = (NestedMarkupWriter)_writers.get(key);
486    
487                nw.close();
488            }
489    
490            _writer.flush();
491        }
492    
493        /**
494         * Grabs the incoming parameters needed for json responses, most notable the
495         * {@link ServiceConstants#UPDATE_PARTS} parameter.
496         *
497         * @param cycle
498         *            The request cycle to parse from
499         */
500        void parseParameters(IRequestCycle cycle)
501        {
502            Object[] updateParts = cycle.getParameters(ServiceConstants.UPDATE_PARTS);
503    
504            if (updateParts == null)
505                return;
506    
507            for(int i = 0; i < updateParts.length; i++)
508            {
509                _parts.add(updateParts[i].toString());
510            }
511        }
512    
513        /**
514         * Determines if the specified component is contained in the
515         * responses requested update parts.
516         * @param target
517         *          The component to check for.
518         * @return True if the request should capture the components output.
519         */
520        public boolean contains(IComponent target)
521        {
522            if (target == null)
523                return false;
524    
525            String id = target.getClientId();
526    
527            return contains(target, id);
528        }
529    
530        boolean contains(IComponent target, String id)
531        {
532            if (_parts.contains(id))
533                return true;
534    
535            Iterator it = _cycle.renderStackIterator();
536            while (it.hasNext())
537            {
538                IComponent comp = (IComponent)it.next();
539                String compId = comp.getClientId();
540    
541                if (comp != target && _parts.contains(compId))
542                    return true;
543            }
544    
545            return false;
546        }
547    
548        /**
549         * {@inheritDoc}
550         */
551        public boolean explicitlyContains(IComponent target)
552        {
553            if (target == null)
554                return false;
555    
556            return _parts.contains(target.getId());
557        }
558    }