All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.tomahawk.application.jsp.JspTilesTwoViewHandlerImpl Maven / Gradle / Ivy

Go to download

JSF components and utilities that can be used with any JSF implementation. This library is based on the JSF1.1 version of Tomahawk, but with minor source code and build changes to take advantage of JSF2.1 features. A JSF2.1 implementation is required to use this version of the Tomahawk library.

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.myfaces.tomahawk.application.jsp;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tiles.access.TilesAccess;
import org.apache.tiles.context.BasicAttributeContext;
import org.apache.tiles.context.TilesRequestContext;
import org.apache.tiles.definition.NoSuchDefinitionException;
import org.apache.tiles.factory.TilesContainerFactory;
import org.apache.tiles.impl.BasicTilesContainer;
import org.apache.tiles.preparer.NoSuchPreparerException;
import org.apache.tiles.preparer.ViewPreparer;
import org.apache.tiles.servlet.context.ServletTilesApplicationContext;
import org.apache.tiles.servlet.context.ServletTilesRequestContext;
import org.apache.tiles.AttributeContext;
import org.apache.tiles.Definition;
import org.apache.tiles.TilesContainer;
import org.apache.tiles.TilesException;
import org.apache.myfaces.shared_tomahawk.config.MyfacesConfig;
import org.apache.myfaces.shared_tomahawk.renderkit.html.util.JavascriptUtils;
import org.apache.myfaces.shared_tomahawk.webapp.webxml.ServletMapping;
import org.apache.myfaces.shared_tomahawk.webapp.webxml.WebXml;

import javax.faces.application.StateManager;
import javax.faces.application.ViewHandler;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.component.UIViewRoot;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;

/**
 * This ViewHandler should be used with myfaces core 1.2.4 or superior
 * and it does not work with jsf ri 1.2 
 * 
 * @since 1.1.7
 * @author Martin Marinschek
 * @version $Revision: 691871 $ $Date: 2008-09-03 23:32:08 -0500 (Wed, 03 Sep 2008) $
 */
public class JspTilesTwoViewHandlerImpl
        extends ViewHandler {
    private ViewHandler _viewHandler;

    public static final String FORM_STATE_MARKER = "";
    public static final int FORM_STATE_MARKER_LEN = FORM_STATE_MARKER.length();
    
    private static final Log log = LogFactory.getLog(JspTilesTwoViewHandlerImpl.class);
    private static final String TILES_DEF_EXT = ".tiles";
    private String tilesExtension = TILES_DEF_EXT;
    private static final String AFTER_VIEW_TAG_CONTENT_PARAM = JspTilesTwoViewHandlerImpl.class + ".AFTER_VIEW_TAG_CONTENT";

    public JspTilesTwoViewHandlerImpl(ViewHandler viewHandler)
    {
        _viewHandler = viewHandler;
    }

    private void initContainer(ExternalContext context) {

        if(TilesAccess.getContainer(context.getContext())==null) {
            try
            {
                TilesContainerFactory factory = TilesContainerFactory.getFactory(context.getContext());
                TilesContainer container = factory.createTilesContainer(context.getContext());
                TilesAccess.setContainer(context.getContext(),container);
            }
            catch (Exception e)
            {
                throw new FacesException("Error reading tiles definitions : " + e.getMessage(), e);
            }
        }
    }

    public void renderView(FacesContext facesContext, UIViewRoot viewToRender) throws IOException, FacesException
    {
        if (viewToRender == null)
        {
            log.fatal("viewToRender must not be null");
            throw new NullPointerException("viewToRender must not be null");
        }

        ExternalContext externalContext = facesContext.getExternalContext();

        String viewId = deriveViewId(externalContext, viewToRender.getViewId());

        if(viewId==null) {
            //deriving view-id made clear we are not responsible for this view-id - call the delegate
            _viewHandler.renderView(facesContext, viewToRender);
            return;
        }


        initContainer(externalContext);

        String tilesId = deriveTileFromViewId(viewId);

        TilesContainer container = TilesAccess.getContainer(externalContext.getContext());

        Object[] requestObjects = {externalContext.getRequest(), externalContext.getResponse()};

        if(container.isValidDefinition(tilesId, requestObjects)) {

            //propagate our new view-id to wherever it makes sense
            setViewId(viewToRender, viewId, facesContext);
            renderTilesView(facesContext, requestObjects, container, viewToRender, viewId, tilesId);
        } else {
            //we're not using tiles as no valid definition has been found
            //just call the delegate view-handler to let it do its thing
            _viewHandler.renderView(facesContext, viewToRender);
        }
    }

    private void renderTilesView(FacesContext facesContext,
            Object[] requestObjects, TilesContainer container,
            UIViewRoot viewToRender, String viewId, String tilesId)
            throws IOException
    {
        ExternalContext externalContext = facesContext.getExternalContext();
        handleCharacterEncoding(viewId, externalContext, viewToRender);
        container.startContext(requestObjects);
        try
        {
            //container.render(tilesId,requestObjects);
            buildTilesViewLikeContainer(externalContext, container, tilesId,
                    requestObjects);
        }
        catch (TilesException e)
        {
            throw new FacesException(e);
        }
        finally
        {
            container.endContext(requestObjects);
        }

        handleCharacterEncodingPostDispatch(externalContext);

        // render the view in this method (since JSF 1.2)
        RenderKitFactory renderFactory = (RenderKitFactory) FactoryFinder
                .getFactory(FactoryFinder.RENDER_KIT_FACTORY);
        RenderKit renderKit = renderFactory.getRenderKit(facesContext,
                viewToRender.getRenderKitId());
        ServletResponse response = (ServletResponse) requestObjects[1];
        ResponseWriter responseWriter = facesContext.getResponseWriter();
        if (responseWriter == null)
        {
            responseWriter = renderKit.createResponseWriter(response
                    .getWriter(), null, ((HttpServletRequest) externalContext
                    .getRequest()).getCharacterEncoding());
            facesContext.setResponseWriter(responseWriter);
        }

        ResponseWriter oldResponseWriter = responseWriter;
        StateMarkerAwareWriter stateAwareWriter = null;

        StateManager stateManager = facesContext.getApplication()
                .getStateManager();
        if (stateManager.isSavingStateInClient(facesContext))
        {
            stateAwareWriter = new StateMarkerAwareWriter();

            // Create a new response-writer using as an underlying writer the stateAwareWriter
            // Effectively, all output will be buffered in the stateAwareWriter so that later
            // this writer can replace the state-markers with the actual state.
            responseWriter = oldResponseWriter
                    .cloneWithWriter(stateAwareWriter);
            facesContext.setResponseWriter(responseWriter);
        }

        actuallyRenderView(facesContext, viewToRender);

        //We're done with the document - now we can write all content
        //to the response, properly replacing the state-markers on the way out
        //by using the stateAwareWriter
        if (stateManager.isSavingStateInClient(facesContext))
        {
            stateAwareWriter.flushToWriter(response.getWriter());
        }
        else
        {
            stateManager.saveView(facesContext);
        }

        // Final step - we output any content in the wrappedResponse response from above to the response,
        // removing the wrappedResponse response from the request, we don't need it anymore
        ViewResponseWrapper afterViewTagResponse = (ViewResponseWrapper) externalContext
                .getRequestMap().get(AFTER_VIEW_TAG_CONTENT_PARAM);
        externalContext.getRequestMap().remove(AFTER_VIEW_TAG_CONTENT_PARAM);
        if (afterViewTagResponse != null)
        {
            afterViewTagResponse.flushToWriter(response.getWriter(),
                    facesContext.getExternalContext()
                            .getResponseCharacterEncoding());
        }
        response.flushBuffer();
    }
    
    
    private String deriveTileFromViewId(String viewId) {
        String tilesId = viewId;
        int idx = tilesId.lastIndexOf('.');
        if (idx > 0)
        {
            tilesId = tilesId.substring(0, idx) + tilesExtension;
        }
        else
        {
            tilesId = tilesId  + tilesExtension;
        }
        return tilesId;
    }

    private String deriveViewId(ExternalContext externalContext, String viewId) {
        ServletMapping servletMapping = getServletMapping(externalContext);

        String defaultSuffix = externalContext.getInitParameter(ViewHandler.DEFAULT_SUFFIX_PARAM_NAME);
        String suffix = defaultSuffix != null ? defaultSuffix : ViewHandler.DEFAULT_SUFFIX;
        if (servletMapping.isExtensionMapping())
        {
            if (!viewId.endsWith(suffix))
            {
                int dot = viewId.lastIndexOf('.');
                if (dot == -1)
                {
                    if (log.isTraceEnabled()) log.trace("Current viewId has no extension, appending default suffix " + suffix);
                    return viewId + suffix;
                }
                else
                {
                    if (log.isTraceEnabled()) log.trace("Replacing extension of current viewId by suffix " + suffix);
                    return viewId.substring(0, dot) + suffix;
                }
            }

            //extension-mapped page ends with proper suffix - all ok
            return viewId;
        }
        else if (!viewId.endsWith(suffix))
        {
            // path-mapping used, but suffix is no default-suffix
            return null;
        }
        else {
            //path-mapping used, suffix is default-suffix - all ok
            return viewId;
        }
    }

    private void handleCharacterEncodingPostDispatch(ExternalContext externalContext) {
        // handle character encoding as of section 2.5.2.2 of JSF 1.1
        if (externalContext.getRequest() instanceof HttpServletRequest) {
            HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();
            HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
            HttpSession session = request.getSession(false);

            if (session != null) {
                session.setAttribute(ViewHandler.CHARACTER_ENCODING_KEY, response.getCharacterEncoding());
            }
        }
    }

    private void handleCharacterEncoding(String viewId, ExternalContext externalContext, UIViewRoot viewToRender) {
        if (log.isTraceEnabled()) log.trace("Dispatching to " + viewId);

        // handle character encoding as of section 2.5.2.2 of JSF 1.1
        if (externalContext.getResponse() instanceof ServletResponse) {
            ServletResponse response = (ServletResponse) externalContext.getResponse();
            response.setLocale(viewToRender.getLocale());
        }
    }
    
    private void buildTilesViewLikeContainer(ExternalContext externalContext,
            TilesContainer container, String definitionName,
            Object... requestItems) throws TilesException
    {
        if (log.isDebugEnabled())
        {
            log.debug("Render request recieved for definition '"
                    + definitionName + "'");
        }
        TilesRequestContext tilesRequest = ((BasicTilesContainer) container)
                .getContextFactory().createRequestContext(
                        container.getApplicationContext(), requestItems);
        Definition definition = ((BasicTilesContainer) container)
                .getDefinitionsFactory().getDefinition(definitionName,
                        tilesRequest);
        if (definition == null)
        {
            if (log.isWarnEnabled())
            {
                String message = "Unable to find the definition '"
                        + definitionName + "'";
                log.warn(message);
            }
            throw new NoSuchDefinitionException(definitionName);
        }
        if (!isPermitted(tilesRequest, definition.getRole()))
        {
            log.info("Access to definition '" + definitionName
                    + "' denied. User not in role '" + definition.getRole());
            return;
        }

        AttributeContext originalContext = container
                .getAttributeContext(requestItems);
        BasicAttributeContext subContext = new BasicAttributeContext(
                originalContext);
        subContext.addMissing(definition.getAttributes());
        BasicAttributeContext.pushContext(subContext, tilesRequest);

        try
        {
            if (definition.getPreparer() != null)
            {
                prepare(container, tilesRequest, definition.getPreparer(), true);
            }
            String dispatchPath = definition.getTemplate();
            if (log.isDebugEnabled())
            {
                log.debug("Dispatching to definition path '"
                        + definition.getTemplate() + " '");
            }

            if (!buildView(container, tilesRequest, externalContext,
                    dispatchPath))
            {
                //building the view was unsuccessful - an exception occurred during rendering
                //we need to jump out
                return;
            }

            // tiles exception so that it doesn't need to be rethrown.
        }
        catch (TilesException e)
        {
            throw e;
        }
        catch (Exception e)
        {
            log.error("Error rendering tile", e);
            throw new TilesException(e.getMessage(), e);
        }
        finally
        {
            BasicAttributeContext.popContext(tilesRequest);
        }
    }
    
    /**
     * Checks if the current user is in one of the comma-separated roles
     * specified in the role parameter.
     *
     * @param request The request context.
     * @param role The comma-separated list of roles.
     * @return true if the current user is in one of those roles.
     */
    private boolean isPermitted(TilesRequestContext request, String role) {
        if (role == null) {
            return true;
        }
        StringTokenizer st = new StringTokenizer(role, ",");
        while (st.hasMoreTokens()) {
            if (request.isUserInRole(st.nextToken())) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Execute a preparer.
     *
     * @param context The request context.
     * @param preparerName The name of the preparer.
     * @param ignoreMissing If true if the preparer is not found,
     * it ignores the problem.
     * @throws TilesException If the preparer is not found (and
     * ignoreMissing is not set) or if the preparer itself threw an
     * exception.
     */
    private void prepare(TilesContainer container, TilesRequestContext context, String preparerName, boolean ignoreMissing) throws TilesException {
        if (log.isDebugEnabled()) {
            log.debug("Prepare request received for '" + preparerName);
        }
        ViewPreparer preparer = ((BasicTilesContainer)container).getPreparerFactory().getPreparer(preparerName, context);
        if (preparer == null && ignoreMissing) {
            return;
        }
        if (preparer == null) {
            throw new NoSuchPreparerException("Preparer '" + preparerName + " not found");
        }
        AttributeContext attributeContext = BasicAttributeContext.getContext(context);
        preparer.execute(context, attributeContext);
    }
    
    /**Build the view-tree before rendering.
     * This is done by dispatching to the underlying JSP-page, effectively processing it, creating
     * components out of any text in between JSF components (not rendering the text to the output of course, this
     * will happen later while rendering), attaching these components
     * to the component tree, and buffering any content after the view-root.
     *
     * @param container The current TilesContainer
     * @param tilesRequest The current TilesRequestContext - its response will be replaced while the view-building happens (we want the text in the component tree, not on the actual servlet output stream)
     * @param externalContext The external context where the response will be replaced while building
     * @param viewId The view-id to dispatch to
     * @return true if successfull, false if an error occurred during rendering
     * @throws IOException
     */
    private boolean buildView(TilesContainer container,
            TilesRequestContext tilesRequest, ExternalContext externalContext,
            String viewId) throws IOException
    {
        HttpServletResponse response = (HttpServletResponse) tilesRequest
                .getResponse();
        ViewResponseWrapper wrappedResponse = new ViewResponseWrapper(response);
        tilesRequest = new ServletTilesRequestContext(
                ((ServletTilesApplicationContext) container
                        .getApplicationContext()).getServletContext(),
                (HttpServletRequest) tilesRequest.getRequest(), wrappedResponse);
        tilesRequest.dispatch(viewId);
        tilesRequest = new ServletTilesRequestContext(
                ((ServletTilesApplicationContext) container
                        .getApplicationContext()).getServletContext(),
                (HttpServletRequest) tilesRequest.getRequest(), response);
        boolean errorResponse = wrappedResponse.getStatus() < 200
                || wrappedResponse.getStatus() > 299;
        if (errorResponse)
        {
            wrappedResponse.flushToWrappedResponse();
            return false;
        }
        // store the wrapped response in the request, so it is thread-safe
        externalContext.getRequestMap().put(AFTER_VIEW_TAG_CONTENT_PARAM,
                wrappedResponse);
        return true;
    }
    
    /**
     * Render the view now - properly setting and resetting the response writer
     */
    private void actuallyRenderView(FacesContext facesContext, UIViewRoot viewToRender) throws IOException {
        // Set the new ResponseWriter into the FacesContext, saving the old one aside.
        ResponseWriter responseWriter = facesContext.getResponseWriter();
        //Now we actually render the document
        // Call startDocument() on the ResponseWriter.
        responseWriter.startDocument();
        // Call encodeAll() on the UIViewRoot
        viewToRender.encodeAll(facesContext);
        // Call endDocument() on the ResponseWriter
        responseWriter.endDocument();
        responseWriter.flush();
    }

    private void setViewId(UIViewRoot viewToRender, String viewId, FacesContext facesContext) {
        viewToRender.setViewId(viewId);
        if(facesContext.getViewRoot()!=null) {
            facesContext.getViewRoot().setViewId(viewId);
        }
    }

    private static ServletMapping getServletMapping(ExternalContext externalContext)
    {
        String servletPath = externalContext.getRequestServletPath();
        String requestPathInfo = externalContext.getRequestPathInfo();

        WebXml webxml = WebXml.getWebXml(externalContext);
        List mappings = webxml.getFacesServletMappings();

        boolean isExtensionMapping = requestPathInfo == null;

        for (int i = 0, size = mappings.size(); i < size; i++)
        {
            ServletMapping servletMapping = (ServletMapping) mappings.get(i);
            if (servletMapping.isExtensionMapping() == isExtensionMapping)
            {
                String urlpattern = servletMapping.getUrlPattern();
                if (isExtensionMapping)
                {
                    String extension = urlpattern.substring(1, urlpattern.length());
                    if (servletPath.endsWith(extension))
                    {
                        return servletMapping;
                    }
                }
                else
                {
                    urlpattern = urlpattern.substring(0, urlpattern.length() - 2);
                    // servletPath starts with "/" except in the case where the
                    // request is matched with the "/*" pattern, in which case
                    // it is the empty string (see Servlet Sepc 2.3 SRV4.4)
                    if (servletPath.equals(urlpattern))
                    {
                        return servletMapping;
                    }
                }
            }
        }
        log.error("could not find pathMapping for servletPath = " + servletPath +
                  " requestPathInfo = " + requestPathInfo);
        throw new IllegalArgumentException("could not find pathMapping for servletPath = " + servletPath +
                  " requestPathInfo = " + requestPathInfo);
    }


    public Locale calculateLocale(FacesContext context)
    {
        return _viewHandler.calculateLocale(context);
    }

    public String calculateRenderKitId(FacesContext context)
    {
        return _viewHandler.calculateRenderKitId(context);
    }

    public UIViewRoot createView(FacesContext context, String viewId)
    {
        return _viewHandler.createView(context, viewId);
    }

    public String getActionURL(FacesContext context, String viewId)
    {
        return _viewHandler.getActionURL(context, viewId);
    }

    public String getResourceURL(FacesContext context, String path)
    {
        return _viewHandler.getResourceURL(context, path);
    }

    public UIViewRoot restoreView(FacesContext context, String viewId)
    {
        return _viewHandler.restoreView(context, viewId);
    }

    public void writeState(FacesContext context) throws IOException
    {
        _viewHandler.writeState(context);
    }
    
    /**
     * Writes the response and replaces the state marker tags with the state information for the current context
     */
    private static class StateMarkerAwareWriter extends Writer
    {
        private StringBuilder buf;

        public StateMarkerAwareWriter()
        {
            this.buf = new StringBuilder();
        }

        @Override
        public void close() throws IOException
        {
        }

        @Override
        public void flush() throws IOException
        {
        }

        @Override
        public void write(char[] cbuf, int off, int len) throws IOException
        {
            if ((off < 0) || (off > cbuf.length) || (len < 0)
                    || ((off + len) > cbuf.length) || ((off + len) < 0))
            {
                throw new IndexOutOfBoundsException();
            }
            else if (len == 0)
            {
                return;
            }
            buf.append(cbuf, off, len);
        }

        public StringBuilder getStringBuilder()
        {
            return buf;
        }

        public void flushToWriter(Writer writer) throws IOException
        {
            FacesContext facesContext = FacesContext.getCurrentInstance();
            StateManager stateManager = facesContext.getApplication().getStateManager();

            StringWriter stateWriter = new StringWriter();
            ResponseWriter realWriter = facesContext.getResponseWriter();
            facesContext.setResponseWriter(realWriter.cloneWithWriter(stateWriter));

            Object serializedView = stateManager.saveView(facesContext);

            stateManager.writeState(facesContext, serializedView);
            facesContext.setResponseWriter(realWriter);

            StringBuilder contentBuffer = getStringBuilder();
            String state = stateWriter.getBuffer().toString();

            ExternalContext extContext = facesContext.getExternalContext();
            if (JavascriptUtils.isJavascriptAllowed(extContext) && MyfacesConfig.getCurrentInstance(extContext).isViewStateJavascript()) {
             // If javascript viewstate is enabled no state markers were written
             write(contentBuffer, 0, contentBuffer.length(), writer);
             writer.write(state);
            } else {
             // If javascript viewstate is disabled state markers must be replaced
             int lastFormMarkerPos = 0;
             int formMarkerPos = 0;
             // Find all state markers and write out actual state instead
             while ((formMarkerPos = contentBuffer.indexOf(FORM_STATE_MARKER, formMarkerPos)) > -1)
             {
             // Write content before state marker
             write(contentBuffer, lastFormMarkerPos, formMarkerPos, writer);
             // Write state and move position in buffer after marker
             writer.write(state);
             formMarkerPos += FORM_STATE_MARKER_LEN;
             lastFormMarkerPos = formMarkerPos;
             }
                // Write content after last state marker
                if (lastFormMarkerPos < contentBuffer.length()) {
                 write(contentBuffer, lastFormMarkerPos, contentBuffer.length(), writer);
                }
            }

        }

        /**
         * Writes the content of the specified StringBuffer from index
         * beginIndex to index endIndex - 1.
         *
         * @param contentBuffer the StringBuffer to copy content from
         * @param begin the beginning index, inclusive.
         * @param end the ending index, exclusive
         * @param writer the Writer to write to
         * @throws IOException if an error occurs writing to specified Writer
         */
        private void write(StringBuilder contentBuffer, int beginIndex, int endIndex, Writer writer) throws IOException {
            int index = beginIndex;
            int bufferSize = 2048;
            char[] bufToWrite = new char[bufferSize];

            while (index < endIndex)
            {
                int maxSize = Math.min(bufferSize, endIndex - index);

                contentBuffer.getChars(index, index + maxSize, bufToWrite, 0);
                writer.write(bufToWrite, 0, maxSize);

                index += bufferSize;
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy