Package org.apache.tapestry.services.impl

Source Code of org.apache.tapestry.services.impl.DojoAjaxResponseBuilder

// Copyright May 8, 2006 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry.services.impl;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hivemind.Resource;
import org.apache.hivemind.util.Defense;
import org.apache.tapestry.*;
import org.apache.tapestry.asset.AssetFactory;
import org.apache.tapestry.engine.IEngineService;
import org.apache.tapestry.engine.NullWriter;
import org.apache.tapestry.markup.MarkupWriterSource;
import org.apache.tapestry.markup.NestedMarkupWriterImpl;
import org.apache.tapestry.services.RequestLocaleManager;
import org.apache.tapestry.services.ResponseBuilder;
import org.apache.tapestry.services.ServiceConstants;
import org.apache.tapestry.util.ContentType;
import org.apache.tapestry.util.PageRenderSupportImpl;
import org.apache.tapestry.util.ScriptUtils;
import org.apache.tapestry.web.WebResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;


/**
* Main class that handles dojo based ajax responses. These responses are wrapped
* by an xml document format that segments off invididual component/javascript response
* types into easy to manage xml elements that can then be interpreted and managed by
* running client-side javascript.
*
*/
public class DojoAjaxResponseBuilder implements ResponseBuilder
{
    private static final Log _log = LogFactory.getLog(DojoAjaxResponseBuilder.class);

    private static final String NEWLINE = System.getProperty("line.separator");

    private final AssetFactory _assetFactory;

    private final String _namespace;

    private PageRenderSupportImpl _prs;

    // used to create IMarkupWriter
    private RequestLocaleManager _localeManager;
    private MarkupWriterSource _markupWriterSource;
    private WebResponse _response;

    private List _errorPages;

    private ContentType _contentType;

    // our response writer
    private IMarkupWriter _writer;
    // Parts that will be updated.
    private List _parts = new ArrayList();
    // Map of specialized writers, like scripts
    private Map _writers = new HashMap();
    // List of status messages.
    private List _statusMessages;

    private IRequestCycle _cycle;

    private IEngineService _pageService;

    /**
     * Keeps track of renders involving a whole page response, such
     * as exception pages or pages activated via {@link IRequestCycle#activate(IPage)}.
     */
    private boolean _pageRender = false;

    /**
     * Used to keep track of whether or not the appropriate xml response start
     * block has been started.
     */
    private boolean _responseStarted = false;

    /**
     * Creates a builder with a pre-configured {@link IMarkupWriter}.
     * Currently only used for testing.
     *
     * @param cycle
     *          The current cycle.
     * @param writer
     *          The markup writer to render all "good" content to.
     * @param parts
     *          A set of string ids of the components that may have
     *          their responses rendered.
     * @param errorPages
     *          List of page names known to be exception pages.
     */
    public DojoAjaxResponseBuilder(IRequestCycle cycle, IMarkupWriter writer, List parts, List errorPages)
    {
        Defense.notNull(cycle, "cycle");
        Defense.notNull(writer, "writer");

        _writer = writer;
        _cycle = cycle;

        if (parts != null)
            _parts.addAll(parts);

        _namespace = null;
        _assetFactory = null;
        _errorPages = errorPages;
    }

    /**
     * Creates a builder with a pre-configured {@link IMarkupWriter}.
     * Currently only used for testing.
     *
     * @param cycle
     *          Current request.
     * @param writer
     *          The markup writer to render all "good" content to.
     * @param parts
     *          A set of string ids of the components that may have
     *          their responses rendered.
     */
    public DojoAjaxResponseBuilder(IRequestCycle cycle, IMarkupWriter writer, List parts)
    {
        this(cycle, writer, parts, null);
    }

    /**
     * Creates a new response builder with the required services it needs
     * to render the response when {@link #renderResponse(IRequestCycle)} is called.
     *
     * @param cycle
     *          The current request.
     * @param localeManager
     *          Used to set the locale on the response.
     * @param markupWriterSource
     *          Creates IJSONWriter instance to be used.
     * @param webResponse
     *          Web response for output stream.
     * @param errorPages
     *          List of page names known to be exception pages.
     * @param assetFactory
     *          Used to manage asset source inclusions.
     * @param namespace
     *          The core namespace to use for javascript/client side operations.
     * @param pageService
     *          {@link org.apache.tapestry.engine.PageService} used to generate page urls.
     */
    public DojoAjaxResponseBuilder(IRequestCycle cycle,
                                   RequestLocaleManager localeManager,
                                   MarkupWriterSource markupWriterSource,
                                   WebResponse webResponse, List errorPages,
                                   AssetFactory assetFactory, String namespace, IEngineService pageService)
    {
        Defense.notNull(cycle, "cycle");
        Defense.notNull(assetFactory, "assetService");

        _cycle = cycle;
        _localeManager = localeManager;
        _markupWriterSource = markupWriterSource;
        _response = webResponse;
        _errorPages = errorPages;
        _pageService = pageService;

        // Used by PageRenderSupport

        _assetFactory = assetFactory;
        _namespace = namespace;
    }

    /**
     *
     * {@inheritDoc}
     */
    public boolean isDynamic()
    {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public void renderResponse(IRequestCycle cycle)
      throws IOException
    {
        // if response was already started

        if (_responseStarted)
        {
            // clear out any previous input
            clearPartialWriters();

            cycle.renderPage(this);

            TapestryUtils.removePageRenderSupport(cycle);
           
            endResponse();

            _writer.close();

            return;
        }

        _localeManager.persistLocale();
        _contentType = new ContentType(CONTENT_TYPE + ";charset=" + cycle.getInfrastructure().getOutputEncoding());

        String encoding = _contentType.getParameter(ENCODING_KEY);

        if (encoding == null)
        {
            encoding = cycle.getEngine().getOutputEncoding();

            _contentType.setParameter(ENCODING_KEY, encoding);
        }

        if (_writer == null)
        {
            parseParameters(cycle);

            PrintWriter printWriter = _response.getPrintWriter(_contentType);
            _writer = _markupWriterSource.newMarkupWriter(printWriter, _contentType);
        }

        // render response

        _prs = new PageRenderSupportImpl(_assetFactory, _namespace, cycle.getPage().getLocation(), this);

        TapestryUtils.storePageRenderSupport(cycle, _prs);

        cycle.renderPage(this);

        TapestryUtils.removePageRenderSupport(cycle);

        endResponse();

        _writer.close();
    }

    public void flush()
      throws IOException
    {
        // Important - causes any cookies stored to properly be written out before the
        // rest of the response starts being written - see TAPESTRY-825

        _writer.flush();

        if (!_responseStarted)
            beginResponse();
    }

    /**
     * {@inheritDoc}
     */
    public void updateComponent(String id)
    {
        if (!_parts.contains(id))
            _parts.add(id);
    }

    /**
     * {@inheritDoc}
     */
    public IMarkupWriter getWriter()
    {
        return _writer;
    }

    void setWriter(IMarkupWriter writer)
    {
        _writer = writer;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isBodyScriptAllowed(IComponent target)
    {
        if (_pageRender)
            return true;

        if (target != null
            && IPage.class.isInstance(target)
            || (IForm.class.isInstance(target)
                && ((IForm)target).isFormFieldUpdating()))
            return true;

        return contains(target);
    }

    /**
     * {@inheritDoc}
     */
    public boolean isExternalScriptAllowed(IComponent target)
    {
        if (_pageRender)
            return true;

        if (target != null
            && IPage.class.isInstance(target)
            || (IForm.class.isInstance(target)
                && ((IForm)target).isFormFieldUpdating()))
            return true;

        return contains(target);
    }

    /**
     * {@inheritDoc}
     */
    public boolean isInitializationScriptAllowed(IComponent target)
    {
        if (_log.isDebugEnabled())
        {
            _log.debug("isInitializationScriptAllowed(" + target + ") contains?: " + contains(target) + " _pageRender: " + _pageRender);
        }

        if (_pageRender)
            return true;

        if (target != null
            && IPage.class.isInstance(target)
            || (IForm.class.isInstance(target)
                && ((IForm)target).isFormFieldUpdating()))
            return true;

        return contains(target);
    }

    /**
     * {@inheritDoc}
     */
    public boolean isImageInitializationAllowed(IComponent target)
    {
        if (_pageRender)
            return true;

        if (target != null
            && IPage.class.isInstance(target)
            || (IForm.class.isInstance(target)
                && ((IForm)target).isFormFieldUpdating()))
            return true;

        return contains(target);
    }

    /**
     * {@inheritDoc}
     */
    public String getPreloadedImageReference(IComponent target, IAsset source)
    {
        return _prs.getPreloadedImageReference(target, source);
    }

    /**
     * {@inheritDoc}
     */
    public String getPreloadedImageReference(IComponent target, String url)
    {
        return _prs.getPreloadedImageReference(target, url);
    }

    /**
     * {@inheritDoc}
     */
    public String getPreloadedImageReference(String url)
    {
        return _prs.getPreloadedImageReference(url);
    }

    /**
     * {@inheritDoc}
     */
    public void addBodyScript(IComponent target, String script)
    {
        _prs.addBodyScript(target, script);
    }

    /**
     * {@inheritDoc}
     */
    public void addBodyScript(String script)
    {
        _prs.addBodyScript(script);
    }

    /**
     * {@inheritDoc}
     */
    public void addExternalScript(IComponent target, Resource resource)
    {
        _prs.addExternalScript(target, resource);
    }

    /**
     * {@inheritDoc}
     */
    public void addExternalScript(Resource resource)
    {
        _prs.addExternalScript(resource);
    }

    /**
     * {@inheritDoc}
     */
    public void addInitializationScript(IComponent target, String script)
    {
        _prs.addInitializationScript(target, script);
    }

    /**
     * {@inheritDoc}
     */
    public void addInitializationScript(String script)
    {
        _prs.addInitializationScript(script);
    }

    public void addScriptAfterInitialization(IComponent target, String script)
    {
        _prs.addScriptAfterInitialization(target, script);
    }

    /**
     * {@inheritDoc}
     */
    public String getUniqueString(String baseValue)
    {
        return _prs.getUniqueString(baseValue);
    }

    /**
     * {@inheritDoc}
     */
    public void writeBodyScript(IMarkupWriter writer, IRequestCycle cycle)
    {
        _prs.writeBodyScript(writer, cycle);
    }

    /**
     * {@inheritDoc}
     */
    public void writeInitializationScript(IMarkupWriter writer)
    {
        _prs.writeInitializationScript(writer);
    }

    /**
     * {@inheritDoc}
     */
    public void beginBodyScript(IMarkupWriter normalWriter, IRequestCycle cycle)
    {
        IMarkupWriter writer = getWriter(ResponseBuilder.BODY_SCRIPT, ResponseBuilder.SCRIPT_TYPE);

        writer.begin("script");
        writer.printRaw(NEWLINE + "//<![CDATA[" + NEWLINE);
    }

    /**
     * {@inheritDoc}
     */
    public void endBodyScript(IMarkupWriter normalWriter, IRequestCycle cycle)
    {
        IMarkupWriter writer = getWriter(ResponseBuilder.BODY_SCRIPT, ResponseBuilder.SCRIPT_TYPE);

        writer.printRaw(NEWLINE + "//]]>" + NEWLINE);
        writer.end();
    }

    /**
     * {@inheritDoc}
     */
    public void writeBodyScript(IMarkupWriter normalWriter, String script, IRequestCycle cycle)
    {
        IMarkupWriter writer = getWriter(ResponseBuilder.BODY_SCRIPT, ResponseBuilder.SCRIPT_TYPE);

        writer.printRaw(script);
    }

    /**
     * {@inheritDoc}
     */
    public void writeExternalScript(IMarkupWriter normalWriter, String url, IRequestCycle cycle)
    {
        IMarkupWriter writer = getWriter(ResponseBuilder.INCLUDE_SCRIPT, ResponseBuilder.SCRIPT_TYPE);

        // causes asset includes to be loaded dynamically into document head
        writer.beginEmpty("include");
        writer.attribute("url", url);
    }

    /**
     * {@inheritDoc}
     */
    public void writeImageInitializations(IMarkupWriter normalWriter, String script, String preloadName, IRequestCycle cycle)
    {
        IMarkupWriter writer = getWriter(ResponseBuilder.BODY_SCRIPT, ResponseBuilder.SCRIPT_TYPE);

        writer.printRaw(NEWLINE + preloadName + " = [];" + NEWLINE);
        writer.printRaw("if (document.images) {" + NEWLINE);

        writer.printRaw(script);

        writer.printRaw("}" + NEWLINE);
    }

    /**
     * {@inheritDoc}
     */
    public void writeInitializationScript(IMarkupWriter normalWriter, String script)
    {
        IMarkupWriter writer = getWriter(ResponseBuilder.INITIALIZATION_SCRIPT, ResponseBuilder.SCRIPT_TYPE);

        writer.begin("script");

        // return is in XML so must escape any potentially non-xml compliant content
        writer.printRaw(NEWLINE + "//<![CDATA[" + NEWLINE);

        writer.printRaw(script);

        writer.printRaw(NEWLINE + "//]]>" + NEWLINE);

        writer.end();
    }

    public void addStatus(IMarkupWriter normalWriter, String text)
    {
        addStatusMessage(normalWriter, "info", text);
    }

    /**
     * Adds a status message to the current response. This implementation keeps track
     * of all messages and appends them to the XHR response. On the client side,
     * the default behavior is to publish the message to a topic matching the category name
     * using <code>dojo.event.topic.publish(category,text);</code>.
     *
     * @param normalWriter
     *          The markup writer to use, this may be ignored or swapped
     *          out for a different writer depending on the implementation being used.
     * @param category
     *          Allows setting a category that best describes the type of the status message,
     *          i.e. info, error, e.t.c.
     * @param text
     *          The status message.
     */
    public void addStatusMessage(IMarkupWriter normalWriter, String category, String text)
    {
        if (_statusMessages==null)
        {
            _statusMessages = new ArrayList();
        }

        _statusMessages.add(category);
        _statusMessages.add(text);
    }

    void writeStatusMessages() {

        for (int i=0; i < _statusMessages.size(); i+=2)
        {
            IMarkupWriter writer = getWriter((String) _statusMessages.get(i), "status");

            writer.printRaw((String) _statusMessages.get(i+1));
        }

        _statusMessages = null;
    }

    /**
     * {@inheritDoc}
     */
    public void render(IMarkupWriter writer, IRender render, IRequestCycle cycle)
    {
        // must be a valid writer already

        if (NestedMarkupWriterImpl.class.isInstance(writer) && !NullWriter.class.isInstance(writer))
        {
            render.render(writer, cycle);
            return;
        }

        // check for page exception renders and write content to writer so client can display them

        if (IPage.class.isInstance(render))
        {
            IPage page = (IPage)render;
            String errorPage = getErrorPage(page.getPageName());

            if (errorPage != null)
            {
                _pageRender = true;

                clearPartialWriters();
                render.render(getWriter(errorPage, EXCEPTION_TYPE), cycle);
                return;
            }

            // If a page other than the active page originally requested is rendered
            // it means someone activated a new page, so we need to tell the client to handle
            // this appropriately. (usually by replacing the current dom with whatever this renders)

            if (_cycle.getParameter(ServiceConstants.PAGE) != null
                && !page.getPageName().equals(_cycle.getParameter(ServiceConstants.PAGE)))
            {
                IMarkupWriter urlwriter = _writer.getNestedWriter();

                urlwriter.begin("response");
                urlwriter.attribute("type", PAGE_TYPE);
                urlwriter.attribute("url", _pageService.getLink(true, page.getPageName()).getAbsoluteURL());

                _writers.put(PAGE_TYPE, urlwriter);
                return;
            }
        }

        if (IComponent.class.isInstance(render)
            && contains((IComponent)render, ((IComponent)render).peekClientId()))
        {
            render.render(getComponentWriter( ((IComponent)render).peekClientId() ), cycle);
            return;
        }

        // Nothing else found, throw out response

        render.render(NullWriter.getSharedInstance(), cycle);
    }

    private String getErrorPage(String pageName)
    {
        for (int i=0; i < _errorPages.size(); i++)
        {
            String page = (String)_errorPages.get(i);

            if (pageName.indexOf(page) > -1)
                return page;
        }

        return null;
    }

    IMarkupWriter getComponentWriter(String id)
    {
        return getWriter(id, ELEMENT_TYPE);
    }

    /**
     *
     * {@inheritDoc}
     */
    public IMarkupWriter getWriter(String id, String type)
    {
        Defense.notNull(id, "id can't be null");

        if (!_responseStarted)
            beginResponse();

        IMarkupWriter w = (IMarkupWriter)_writers.get(id);
        if (w != null)
            return w;

        // Make component write to a "nested" writer
        // so that element begin/ends don't conflict
        // with xml element response begin/ends. This is very
        // important.

        IMarkupWriter nestedWriter = _writer.getNestedWriter();
        nestedWriter.begin("response");
        nestedWriter.attribute("id", id);
        if (type != null)
            nestedWriter.attribute("type", type);

        _writers.put(id, nestedWriter);

        return nestedWriter;
    }

    /**
     * Called to start an ajax response. Writes xml doctype and starts
     * the <code>ajax-response</code> element that will contain all of
     * the returned content.
     */
    void beginResponse()
    {
        _responseStarted = true;

        _writer.printRaw("<?xml version=\"1.0\" encoding=\"" + _cycle.getInfrastructure().getOutputEncoding() + "\"?>");
        _writer.printRaw("<!DOCTYPE html "
                         + "PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
                         + "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\" [" + NEWLINE
                         + "<!ENTITY nbsp '&#160;'>" + NEWLINE
                         + "]>" + NEWLINE);
       
        _writer.printRaw("<ajax-response>");
    }

    /**
     * Invoked to clear out tempoary partial writer buffers before rendering exception
     * page.
     */
    void clearPartialWriters()
    {
        _writers.clear();
    }

    /**
     * Called after the entire response has been captured. Causes
     * the writer buffer output captured to be segmented and written
     * out to the right response elements for the client libraries to parse.
     */
    void endResponse()
    {
        if (!_responseStarted)
        {
            beginResponse();
        }

        // write out captured content

        if (_statusMessages != null)
            writeStatusMessages();

        Iterator keys = _writers.keySet().iterator();
        String buffer;

        while (keys.hasNext())
        {
            String key = (String)keys.next();
            NestedMarkupWriter nw = (NestedMarkupWriter)_writers.get(key);

            buffer = nw.getBuffer();

            if (_log.isDebugEnabled())
            {
                _log.debug("Ajax markup buffer for key <" + key + " contains: " + buffer);
            }

            if (!isScriptWriter(key))
                _writer.printRaw(ScriptUtils.ensureValidScriptTags(buffer));
            else
                _writer.printRaw(buffer);
        }

        // end response

        _writer.printRaw("</ajax-response>");
        _writer.flush();
    }

    /**
     * Determines if the specified markup writer key is one of
     * the pre-defined script keys from ResponseBuilder.
     *
     * @param key
     *          The key to check.
     * @return True, if key is one of the ResponseBuilder keys.
     *         (BODY_SCRIPT,INCLUDE_SCRIPT,INITIALIZATION_SCRIPT)
     */
    boolean isScriptWriter(String key)
    {
        if (key == null)
            return false;

        if (ResponseBuilder.BODY_SCRIPT.equals(key)
            || ResponseBuilder.INCLUDE_SCRIPT.equals(key)
            || ResponseBuilder.INITIALIZATION_SCRIPT.equals(key))
            return true;

        return false;
    }

    /**
     * Grabs the incoming parameters needed for json responses, most notable the
     * {@link ServiceConstants#UPDATE_PARTS} parameter.
     *
     * @param cycle
     *            The request cycle to parse from
     */
    void parseParameters(IRequestCycle cycle)
    {
        Object[] updateParts = cycle.getParameters(ServiceConstants.UPDATE_PARTS);

        if (updateParts == null)
            return;

        for(int i = 0; i < updateParts.length; i++)
            _parts.add(updateParts[i].toString());
    }

    /**
     * Determines if the specified component is contained in the
     * responses requested update parts.
     * @param target
     *          The component to check for.
     * @return True if the request should capture the components output.
     */
    public boolean contains(IComponent target)
    {
        if (target == null)
            return false;

        String id = target.getClientId();

        return contains(target, id);
    }

    boolean contains(IComponent target, String id)
    {
        if (_parts.contains(id))
            return true;

        Iterator it = _cycle.renderStackIterator();
        while (it.hasNext())
        {
            IComponent comp = (IComponent)it.next();
            String compId = comp.getClientId();

            if (comp != target && _parts.contains(compId))
                return true;
        }

        return false;
    }

    /**
     * {@inheritDoc}
     */
    public boolean explicitlyContains(IComponent target)
    {
        if (target == null)
            return false;

        return _parts.contains(target.getId());
    }
}
TOP

Related Classes of org.apache.tapestry.services.impl.DojoAjaxResponseBuilder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.