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    
107        /**
108         *
109         * {@inheritDoc}
110         */
111        public boolean isDynamic()
112        {
113            return true;
114        }
115    
116        /**
117         * {@inheritDoc}
118         */
119        public void renderResponse(IRequestCycle cycle)
120            throws IOException
121        {
122            _localeManager.persistLocale();
123    
124            ContentType contentType = new ContentType(CONTENT_TYPE + ";charset=" + cycle.getInfrastructure().getOutputEncoding());
125    
126            String encoding = contentType.getParameter(ENCODING_KEY);
127    
128            if (encoding == null)
129            {
130                encoding = cycle.getEngine().getOutputEncoding();
131    
132                contentType.setParameter(ENCODING_KEY, encoding);
133            }
134    
135            if (_writer == null) {
136    
137                parseParameters(cycle);
138    
139                PrintWriter printWriter = _response.getPrintWriter(contentType);
140    
141                _writer = _markupWriterSource.newMarkupWriter(printWriter, contentType);
142            }
143    
144            // render response
145    
146            _prs = new PageRenderSupportImpl(_assetFactory, _namespace, cycle.getPage().getLocation(), this);
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        /**
316         * {@inheritDoc}
317         */
318        public String getUniqueString(String baseValue)
319        {
320            return _prs.getUniqueString(baseValue);
321        }
322    
323        /**
324         * {@inheritDoc}
325         */
326        public void writeBodyScript(IMarkupWriter writer, IRequestCycle cycle)
327        {
328            _prs.writeBodyScript(writer, cycle);
329        }
330    
331        /**
332         * {@inheritDoc}
333         */
334        public void writeInitializationScript(IMarkupWriter writer)
335        {
336            _prs.writeInitializationScript(writer);
337        }
338    
339        /**
340         * {@inheritDoc}
341         */
342        public void beginBodyScript(IMarkupWriter normalWriter, IRequestCycle cycle)
343        {
344            _writer.begin("script");
345            _writer.printRaw("\n//<![CDATA[\n");
346        }
347    
348        /**
349         * {@inheritDoc}
350         */
351        public void endBodyScript(IMarkupWriter normalWriter, IRequestCycle cycle)
352        {
353            _writer.printRaw("\n//]]>\n");
354            _writer.end();
355        }
356    
357        /**
358         * {@inheritDoc}
359         */
360        public void writeBodyScript(IMarkupWriter normalWriter, String script, IRequestCycle cycle)
361        {
362            _writer.printRaw(script);
363        }
364    
365        /**
366         * {@inheritDoc}
367         */
368        public void writeExternalScript(IMarkupWriter normalWriter, String url, IRequestCycle cycle)
369        {
370            _writer.begin("script");
371            _writer.attribute("type", "text/javascript");
372            _writer.attribute("src", url);
373            _writer.end();
374        }
375    
376        /**
377         * {@inheritDoc}
378         */
379        public void writeImageInitializations(IMarkupWriter normalWriter, String script, String preloadName, IRequestCycle cycle)
380        {
381        }
382    
383        /**
384         * {@inheritDoc}
385         */
386        public void writeInitializationScript(IMarkupWriter normalWriter, String script)
387        {
388            _writer.begin("script");
389    
390            // return is in XML so must escape any potentially non-xml compliant content
391            _writer.printRaw("\n//<![CDATA[\n");
392            _writer.printRaw(script);
393            _writer.printRaw("\n//]]>\n");
394            _writer.end();
395        }
396    
397        public void addStatus(IMarkupWriter normalWriter, String text)
398        {
399            throw new UnsupportedOperationException("Can't return a status response with prototype based requests.");
400        }
401    
402        public void addStatusMessage(IMarkupWriter normalWriter, String category, String text)
403        {
404            throw new UnsupportedOperationException("Can't return a status response with prototype based requests.");
405        }
406        
407        /**
408         * {@inheritDoc}
409         */
410        public void render(IMarkupWriter writer, IRender render, IRequestCycle cycle)
411        {
412            // must be a valid writer already
413    
414            if (NestedMarkupWriterImpl.class.isInstance(writer)) {
415                render.render(writer, cycle);
416                return;
417            }
418    
419            if (IComponent.class.isInstance(render)
420                && contains((IComponent)render, ((IComponent)render).peekClientId()))
421            {
422                render.render(getComponentWriter( ((IComponent)render).peekClientId() ), cycle);
423                return;
424            }
425    
426            // Nothing else found, throw out response
427    
428            render.render(NullWriter.getSharedInstance(), cycle);
429        }
430    
431        IMarkupWriter getComponentWriter(String id)
432        {
433            return getWriter(id, ELEMENT_TYPE);
434        }
435    
436        /**
437         *
438         * {@inheritDoc}
439         */
440        public IMarkupWriter getWriter(String id, String type)
441        {
442            Defense.notNull(id, "id can't be null");
443            
444            IMarkupWriter w = (IMarkupWriter)_writers.get(id);
445            if (w != null)
446                return w;
447            
448            IMarkupWriter nestedWriter = _writer.getNestedWriter();
449            _writers.put(id, nestedWriter);
450    
451            return nestedWriter;
452        }
453    
454        void beginResponse()
455        {
456        }
457    
458        /**
459         * Invoked to clear out tempoary partial writer buffers before rendering exception
460         * page.
461         */
462        void clearPartialWriters()
463        {
464            _writers.clear();
465        }
466    
467        /**
468         * Called after the entire response has been captured. Causes
469         * the writer buffer output captured to be segmented and written
470         * out to the right response elements for the client libraries to parse.
471         */
472        void endResponse()
473        {
474            Iterator keys = _writers.keySet().iterator();
475    
476            while (keys.hasNext()) {
477    
478                String key = (String)keys.next();
479                NestedMarkupWriter nw = (NestedMarkupWriter)_writers.get(key);
480                
481                nw.close();
482            }
483            
484            _writer.flush();
485        }
486    
487        /**
488         * Grabs the incoming parameters needed for json responses, most notable the
489         * {@link ServiceConstants#UPDATE_PARTS} parameter.
490         *
491         * @param cycle
492         *            The request cycle to parse from
493         */
494        void parseParameters(IRequestCycle cycle)
495        {
496            Object[] updateParts = cycle.getParameters(ServiceConstants.UPDATE_PARTS);
497    
498            if (updateParts == null)
499                return;
500    
501            for(int i = 0; i < updateParts.length; i++)
502            {
503                _parts.add(updateParts[i].toString());
504            }
505        }
506    
507        /**
508         * Determines if the specified component is contained in the
509         * responses requested update parts.
510         * @param target
511         *          The component to check for.
512         * @return True if the request should capture the components output.
513         */
514        public boolean contains(IComponent target)
515        {
516            if (target == null)
517                return false;
518    
519            String id = target.getClientId();
520    
521            return contains(target, id);
522        }
523    
524        boolean contains(IComponent target, String id)
525        {
526            if (_parts.contains(id))
527                return true;
528    
529            Iterator it = _cycle.renderStackIterator();
530            while (it.hasNext()) {
531    
532                IComponent comp = (IComponent)it.next();
533                String compId = comp.getClientId();
534    
535                if (comp != target && _parts.contains(compId))
536                    return true;
537            }
538    
539            return false;
540        }
541    
542        /**
543         * {@inheritDoc}
544         */
545        public boolean explicitlyContains(IComponent target)
546        {
547            if (target == null)
548                return false;
549    
550            return _parts.contains(target.getId());
551        }
552    }