Package org.apache.myfaces.test.mock

Source Code of org.apache.myfaces.test.mock.MockFlash$MessageEntry

/*
* 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.test.mock;

import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.Flash;
import javax.faces.event.PhaseId;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

/**
* <p>Mock implementation of <code>Flash</code>.</p>
* <p/>
* $Id: MockFlash.java 990408 2010-08-28 18:59:21Z lu4242 $
*
* @since 1.0.0
*/
public class MockFlash extends Flash
{

    /**
     * Key on app map to keep current instance
     */
    static protected final String FLASH_INSTANCE = MockFlash.class.getName()
            + ".INSTANCE";

    /**
     * Key used to check if there is the current request will be or was redirected
     */
    static protected final String FLASH_REDIRECT = MockFlash.class.getName()
            + ".REDIRECT";

    /**
     * Key used to check if this request should keep messages (like tomahawk sandbox RedirectTracker.
     * Used when post-redirect-get pattern is used)
     */
    static protected final String FLASH_KEEP_MESSAGES = MockFlash.class
            .getName()
            + ".KEEP_MESSAGES";

    static protected final String FLASH_KEEP_MESSAGES_LIST = "KEEPMESSAGESLIST";

    /**
     * Session map prefix to flash maps
     */
    static protected final String FLASH_SCOPE_CACHE = MockFlash.class.getName()
            + ".SCOPE";

    static protected final String FLASH_CURRENT_MAP_CACHE = MockFlash.class
            .getName()
            + ".CURRENTMAP.CACHE";

    static protected final String FLASH_CURRENT_MAP_KEY = MockFlash.class
            .getName()
            + ".CURRENTMAP.KEY";

    static protected final String FLASH_POSTBACK_MAP_CACHE = MockFlash.class
            .getName()
            + ".POSTBACKMAP.CACHE";

    static protected final String FLASH_POSTBACK_MAP_KEY = MockFlash.class
            .getName()
            + ".POSTBACKMAP.KEY";

    static private final char SEPARATOR_CHAR = '.';

    // the current token value
    private final AtomicLong _count;

    public MockFlash()
    {
        _count = new AtomicLong(_getSeed());
    }

    /**
     * @return a cryptographically secure random number to use as the _count seed
     */
    private static long _getSeed()
    {
        SecureRandom rng;
        try
        {
            // try SHA1 first
            rng = SecureRandom.getInstance("SHA1PRNG");
        }
        catch (NoSuchAlgorithmException e)
        {
            // SHA1 not present, so try the default (which could potentially not be
            // cryptographically secure)
            rng = new SecureRandom();
        }

        // use 48 bits for strength and fill them in
        byte[] randomBytes = new byte[6];
        rng.nextBytes(randomBytes);

        // convert to a long
        return new BigInteger(randomBytes).longValue();
    }

    /**
     * @return the next token to be assigned to this request
     */
    protected String _getNextToken()
    {
        // atomically increment the value
        long nextToken = _count.incrementAndGet();

        // convert using base 36 because it is a fast efficient subset of base-64
        return Long.toString(nextToken, 36);
    }

    /**
     * Return a Flash instance from the application map
     *
     * @param context
     * @return
     */
    public static Flash getCurrentInstance(ExternalContext context)
    {
        Map<String, Object> applicationMap = context.getApplicationMap();
        Flash flash = (Flash) applicationMap.get(FLASH_INSTANCE);

        synchronized (applicationMap)
        {
            if (flash == null)
            {
                flash = new MockFlash();
                context.getApplicationMap().put(FLASH_INSTANCE, flash);
            }
        }
        return flash;
    }

    /**
     * Return a wrapper from the session map used to implement flash maps
     * for more information see SubKeyMap doc
     */
    @SuppressWarnings("unchecked")
    private Map<String, Object> _getMapFromSession(FacesContext context,
            String token, boolean createIfNeeded)
    {
        ExternalContext external = context.getExternalContext();
        Object session = external.getSession(true);

        Map<String, Object> map = null;

        // Synchronize on the session object to ensure that
        // we don't ever create two different caches
        synchronized (session)
        {
            map = (Map<String, Object>) external.getSessionMap().get(token);
            if ((map == null) && createIfNeeded)
            {
                map = new MockSubKeyMap<Object>(context.getExternalContext()
                        .getSessionMap(), token);
            }
        }

        return map;
    }

    /**
     * Return the flash map created on this traversal. This one will be sent
     * on next request, so it will be retrieved as postback map of the next
     * request.
     * <p/>
     * Note it is supposed that FLASH_CURRENT_MAP_KEY is initialized before
     * restore view phase (see doPrePhaseActions() for details).
     *
     * @param context
     * @return
     */
    @SuppressWarnings("unchecked")
    protected Map<String, Object> getCurrentRequestMap(FacesContext context)
    {
        Map<String, Object> requestMap = context.getExternalContext()
                .getRequestMap();
        Map<String, Object> map = (Map<String, Object>) requestMap
                .get(FLASH_CURRENT_MAP_CACHE);
        if (map == null)
        {
            String token = (String) requestMap.get(FLASH_CURRENT_MAP_KEY);
            String fullToken = FLASH_SCOPE_CACHE + SEPARATOR_CHAR + token;
            map = _getMapFromSession(context, fullToken, true);
            requestMap.put(FLASH_CURRENT_MAP_CACHE, map);
        }
        return map;
    }

    @SuppressWarnings("unchecked")
    protected Map<String, Object> getPostbackRequestMap(FacesContext context)
    {
        Map<String, Object> requestMap = context.getExternalContext()
                .getRequestMap();
        Map<String, Object> map = (Map<String, Object>) requestMap
                .get(FLASH_POSTBACK_MAP_CACHE);
        if (map == null)
        {
            String token = (String) requestMap.get(FLASH_POSTBACK_MAP_KEY);
            if (token == null && isRedirect())
            {
                // In post-redirect-get, request values are reset, so we need
                // to get the postback key again.
                token = _getPostbackMapKey(context.getExternalContext());
            }
            String fullToken = FLASH_SCOPE_CACHE + SEPARATOR_CHAR + token;
            map = _getMapFromSession(context, fullToken, true);
            requestMap.put(FLASH_POSTBACK_MAP_CACHE, map);
        }
        return map;
    }

    /**
     * Get the proper map according to the current phase:
     * <p/>
     * Normal case:
     * <p/>
     * - First request, restore view phase (create a new one): current map n
     * - First request, execute phase: Skipped
     * - First request, render  phase: current map n
     * <p/>
     * Current map n saved and put as postback map n
     * <p/>
     * - Second request, execute phase: postback map n
     * - Second request, render  phase: current map n+1
     * <p/>
     * Post Redirect Get case: Redirect is triggered by a call to setRedirect(true) from NavigationHandler
     * or earlier using c:set tag.
     * <p/>
     * - First request, restore view phase (create a new one): current map n
     * - First request, execute phase: Skipped
     * - First request, render  phase: current map n
     * <p/>
     * Current map n saved and put as postback map n
     * <p/>
     * POST
     * <p/>
     * - Second request, execute phase: postback map n
     * <p/>
     * REDIRECT
     * <p/>
     * - NavigationHandler do the redirect, requestMap data lost, called Flash.setRedirect(true)
     * <p/>
     * Current map n saved and put as postback map n
     * <p/>
     * GET
     * <p/>
     * - Third  request, restore view phase (create a new one): current map n+1
     * (isRedirect() should return true as javadoc says)
     * - Third  request, execute phase: skipped
     * - Third  request, render  phase: current map n+1
     * <p/>
     * In this way proper behavior is preserved even in the case of redirect, since the GET part is handled as
     * the "render" part of the current traversal, keeping the semantic of flash object.
     *
     * @return
     */
    private Map<String, Object> getCurrentPhaseMap()
    {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        if (PhaseId.RENDER_RESPONSE.equals(facesContext.getCurrentPhaseId())
                || !facesContext.isPostback() || isRedirect())
        {
            return getCurrentRequestMap(facesContext);
        }
        else
        {
            return getPostbackRequestMap(facesContext);
        }
    }

    private void _removeAllChildren(FacesContext facesContext)
    {
        Map<String, Object> map = getPostbackRequestMap(facesContext);

        // Clear everything - note that because of naming conventions,
        // this will in fact automatically recurse through all children
        // grandchildren etc. - which is kind of a design flaw of SubKeyMap,
        // but one we're relying on
        map.clear();
    }

    /**
     *
     */
    @Override
    public void doPrePhaseActions(FacesContext facesContext)
    {
        Map<String, Object> requestMap = facesContext.getExternalContext()
                .getRequestMap();

        if (PhaseId.RESTORE_VIEW.equals(facesContext.getCurrentPhaseId()))
        {
            // Generate token and put on requestMap
            // It is necessary to set this token always, because on the next request
            // it should be possible to change postback map.
            String currentToken = _getNextToken();
            requestMap.put(FLASH_CURRENT_MAP_KEY, currentToken);

            if (facesContext.isPostback())
            {
                //Retore token
                String previousToken = _getPostbackMapKey(facesContext
                        .getExternalContext());

                if (previousToken != null)
                {
                    requestMap.put(FLASH_POSTBACK_MAP_KEY, previousToken);
                }
            }

            if (isKeepMessages())
            {
                restoreMessages(facesContext);
            }
        }

        //
        if (PhaseId.RENDER_RESPONSE.equals(facesContext.getCurrentPhaseId()))
        {
            // Put current map on next previous map
            // but only if this request is not a redirect
            if (!isRedirect())
            {
                _addPostbackMapKey(facesContext.getExternalContext());
            }
        }
    }

    @Override
    public void doPostPhaseActions(FacesContext facesContext)
    {
        if (PhaseId.RENDER_RESPONSE.equals(facesContext.getCurrentPhaseId()))
        {
            //Remove previous flash from session
            Map<String, Object> requestMap = facesContext.getExternalContext()
                    .getRequestMap();
            String token = (String) requestMap.get(FLASH_POSTBACK_MAP_KEY);

            if (token != null)
            {
                _removeAllChildren(facesContext);
            }

            if (isKeepMessages())
            {
                saveMessages(facesContext);
            }
        }
        else if (isRedirect()
                && (facesContext.getResponseComplete() || facesContext
                        .getRenderResponse()))
        {
            if (isKeepMessages())
            {
                saveMessages(facesContext);
            }
        }
    }

    private static class MessageEntry implements Serializable
    {
        private final Object clientId;
        private final Object message;

        public MessageEntry(Object clientId, Object message)
        {
            this.clientId = clientId;
            this.message = message;
        }
    }

    protected void saveMessages(FacesContext facesContext)
    {
        List<MessageEntry> messageList = null;

        Iterator<String> iterClientIds = facesContext
                .getClientIdsWithMessages();
        while (iterClientIds.hasNext())
        {
            String clientId = (String) iterClientIds.next();
            Iterator<FacesMessage> iterMessages = facesContext
                    .getMessages(clientId);

            while (iterMessages.hasNext())
            {
                FacesMessage message = iterMessages.next();

                if (messageList == null)
                {
                    messageList = new ArrayList<MessageEntry>();
                }
                messageList.add(new MessageEntry(clientId, message));
            }
        }

        if (messageList != null)
        {
            if (isRedirect())
            {
                getPostbackRequestMap(facesContext).put(
                        FLASH_KEEP_MESSAGES_LIST, messageList);
            }
            else
            {
                getCurrentRequestMap(facesContext).put(
                        FLASH_KEEP_MESSAGES_LIST, messageList);
            }
        }
    }

    protected void restoreMessages(FacesContext facesContext)
    {
        Map<String, Object> postbackMap = getPostbackRequestMap(facesContext);
        List<MessageEntry> messageList = (List<MessageEntry>) postbackMap
                .get(FLASH_KEEP_MESSAGES_LIST);

        if (messageList != null)
        {
            Iterator iterMessages = messageList.iterator();

            while (iterMessages.hasNext())
            {
                MessageEntry message = (MessageEntry) iterMessages.next();
                facesContext.addMessage((String) message.clientId,
                        (FacesMessage) message.message);
            }

            postbackMap.remove(FLASH_KEEP_MESSAGES_LIST);
        }
    }

    //private void _addPreviousToken

    /**
     * Retrieve the postback map key
     */
    private String _getPostbackMapKey(ExternalContext externalContext)
    {
        String token = null;
        Object response = externalContext.getResponse();
        if (response instanceof HttpServletResponse)
        {
            //Use a cookie
            Cookie cookie = (Cookie) externalContext.getRequestCookieMap().get(
                    FLASH_POSTBACK_MAP_KEY);
            if (cookie != null)
            {
                token = cookie.getValue();
            }
        }
        else
        {
            //Use HttpSession or PortletSession object
            Map<String, Object> sessionMap = externalContext.getSessionMap();
            token = (String) sessionMap.get(FLASH_POSTBACK_MAP_KEY);
        }
        return token;
    }

    /**
     * Take the current map key and store it as a postback key for the next request.
     *
     * @param externalContext
     */
    private void _addPostbackMapKey(ExternalContext externalContext)
    {
        Object response = externalContext.getResponse();
        String token = (String) externalContext.getRequestMap().get(
                FLASH_CURRENT_MAP_KEY);

        //Use HttpSession or PortletSession object
        Map<String, Object> sessionMap = externalContext.getSessionMap();
        sessionMap.put(FLASH_POSTBACK_MAP_KEY, token);
    }

    /**
     * For check if there is a redirect we to take into accout this points:
     * <p/>
     * 1. isRedirect() could be accessed many times during the same
     * request.
     * 2. According to Post-Redirect-Get pattern, we cannot
     * ensure request scope values are preserved.
     */
    @Override
    public boolean isRedirect()
    {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ExternalContext externalContext = facesContext.getExternalContext();
        Map<String, Object> requestMap = externalContext.getRequestMap();
        Boolean redirect = (Boolean) requestMap.get(FLASH_REDIRECT);
        if (redirect == null)
        {
            Object response = externalContext.getResponse();
            if (response instanceof HttpServletResponse)
            {
                // Request values are lost after a redirect. We can create a
                // temporal cookie to pass the params between redirect calls.
                // It is better than use HttpSession object, because this cookie
                // is never sent by the server.
                Cookie cookie = (Cookie) externalContext.getRequestCookieMap()
                        .get(FLASH_REDIRECT);
                if (cookie != null)
                {
                    redirect = Boolean.TRUE;
                    HttpServletResponse httpResponse = (HttpServletResponse) response;
                    // A redirect happened, so it is safe to remove the cookie, setting
                    // the maxAge to 0 seconds. The effect is we passed FLASH_REDIRECT param
                    // to this request object
                    cookie.setMaxAge(0);
                    cookie.setValue(null);
                    httpResponse.addCookie(cookie);
                    requestMap.put(FLASH_REDIRECT, redirect);
                }
                else
                {
                    redirect = Boolean.FALSE;
                }
            }
            else
            {
                // Note that on portlet world we can't create cookies,
                // so we are forced to use the session map. Anyway,
                // according to the Bridge implementation(for example see
                // org.apache.myfaces.portlet.faces.bridge.BridgeImpl)
                // session object is created at start faces request
                Map<String, Object> sessionMap = externalContext
                        .getSessionMap();
                redirect = (Boolean) sessionMap.get(FLASH_REDIRECT);
                if (redirect != null)
                {
                    sessionMap.remove(FLASH_REDIRECT);
                    requestMap.put(FLASH_REDIRECT, redirect);
                }
                else
                {
                    redirect = Boolean.FALSE;
                }
            }
        }
        return redirect;
    }

    @Override
    public void setRedirect(boolean redirect)
    {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ExternalContext externalContext = facesContext.getExternalContext();
        Map<String, Object> requestMap = externalContext.getRequestMap();

        Boolean previousRedirect = (Boolean) requestMap.get(FLASH_REDIRECT);
        previousRedirect = (previousRedirect == null) ? Boolean.FALSE
                : previousRedirect;

        if (!previousRedirect.booleanValue() && redirect)
        {
            // This request contains a redirect. This condition is in general
            // triggered by a NavigationHandler. After a redirect all request scope
            // values get lost, so in order to preserve this value we need to
            // pass it between request. One strategy is use a cookie that is never sent
            // to the client. Other alternative is use the session map.
            externalContext.getSessionMap().put(FLASH_REDIRECT, redirect);
        }
        requestMap.put(FLASH_REDIRECT, redirect);
    }

    /**
     * In few words take a value from request scope map and put it on current request map,
     * so it is visible on the next request.
     */
    @Override
    public void keep(String key)
    {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        Map<String, Object> requestMap = facesContext.getExternalContext()
                .getRequestMap();
        Object value = requestMap.get(key);
        getCurrentRequestMap(facesContext).put(key, value);
    }

    /**
     * This is just an alias for request scope map.
     */
    @Override
    public void putNow(String key, Object value)
    {
        FacesContext.getCurrentInstance().getExternalContext().getRequestMap()
                .put(key, value);
    }

    @Override
    public boolean isKeepMessages()
    {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ExternalContext externalContext = facesContext.getExternalContext();
        Map<String, Object> requestMap = externalContext.getRequestMap();
        Boolean keepMessages = (Boolean) requestMap.get(FLASH_KEEP_MESSAGES);
        if (keepMessages == null)
        {
            Object response = externalContext.getResponse();
            if (response instanceof HttpServletResponse)
            {
                // Request values are lost after a redirect. We can create a
                // temporal cookie to pass the params between redirect calls.
                // It is better than use HttpSession object, because this cookie
                // is never sent by the server.
                Cookie cookie = (Cookie) externalContext.getRequestCookieMap()
                        .get(FLASH_KEEP_MESSAGES);
                if (cookie != null)
                {
                    keepMessages = Boolean.TRUE;
                    HttpServletResponse httpResponse = (HttpServletResponse) response;
                    // It is safe to remove the cookie, setting
                    // the maxAge to 0 seconds. The effect is we passed FLASH_KEEP_MESSAGES param
                    // to this request object
                    cookie.setMaxAge(0);
                    cookie.setValue(null);
                    httpResponse.addCookie(cookie);
                    requestMap.put(FLASH_KEEP_MESSAGES, keepMessages);
                }
                else
                {
                    keepMessages = Boolean.FALSE;
                }
            }
            else
            {
                // Note that on portlet world we can't create cookies,
                // so we are forced to use the session map. Anyway,
                // according to the Bridge implementation(for example see
                // org.apache.myfaces.portlet.faces.bridge.BridgeImpl)
                // session object is created at start faces request
                Map<String, Object> sessionMap = externalContext
                        .getSessionMap();
                keepMessages = (Boolean) sessionMap.get(FLASH_KEEP_MESSAGES);
                if (keepMessages != null)
                {
                    sessionMap.remove(FLASH_KEEP_MESSAGES);
                    requestMap.put(FLASH_KEEP_MESSAGES, keepMessages);
                }
                else
                {
                    keepMessages = Boolean.FALSE;
                }
            }
        }
        return keepMessages;
    }

    /**
     * If this property is true, the messages should be keep for the next request, no matter
     * if it is a normal postback case or a post-redirect-get case.
     */
    @Override
    public void setKeepMessages(boolean keepMessages)
    {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ExternalContext externalContext = facesContext.getExternalContext();
        Map<String, Object> requestMap = externalContext.getRequestMap();

        Boolean previousKeepMessages = (Boolean) requestMap
                .get(FLASH_KEEP_MESSAGES);
        previousKeepMessages = (previousKeepMessages == null) ? Boolean.FALSE
                : previousKeepMessages;

        if (!previousKeepMessages.booleanValue() && keepMessages)
        {
            externalContext.getSessionMap().put(FLASH_KEEP_MESSAGES,
                    keepMessages);
        }
        requestMap.put(FLASH_KEEP_MESSAGES, keepMessages);
    }

    public void clear()
    {
        getCurrentPhaseMap().clear();
    }

    public boolean containsKey(Object key)
    {
        return getCurrentPhaseMap().containsKey(key);
    }

    public boolean containsValue(Object value)
    {
        return getCurrentPhaseMap().containsValue(value);
    }

    public Set<Entry<String, Object>> entrySet()
    {
        return getCurrentPhaseMap().entrySet();
    }

    public Object get(Object key)
    {
        if (key == null)
        {
            return null;
        }

        if ("keepMessages".equals(key))
        {
            return isKeepMessages();
        }
        else if ("redirect".equals(key))
        {
            return isRedirect();
        }

        FacesContext context = FacesContext.getCurrentInstance();
        Map<String, Object> postbackMap = getPostbackRequestMap(context);
        Object returnValue = null;

        if (postbackMap != null)
        {
            returnValue = postbackMap.get(key);
        }

        return returnValue;
    }

    public boolean isEmpty()
    {
        return getCurrentPhaseMap().isEmpty();
    }

    public Set<String> keySet()
    {
        return getCurrentPhaseMap().keySet();
    }

    public Object put(String key, Object value)
    {
        if (key == null)
        {
            return null;
        }

        if ("keepMessages".equals(key))
        {
            Boolean booleanValue = convertToBoolean(value);
            this.setKeepMessages(booleanValue);
            return booleanValue;
        }
        else if ("redirect".equals(key))
        {
            Boolean booleanValue = convertToBoolean(value);
            this.setRedirect(booleanValue);
            return booleanValue;
        }
        else
        {
            Object returnValue = getCurrentPhaseMap().put(key, value);
            return returnValue;
        }
    }

    private Boolean convertToBoolean(Object value)
    {
        Boolean booleanValue;
        if (value instanceof Boolean)
        {
            booleanValue = (Boolean) value;
        }
        else
        {
            booleanValue = Boolean.parseBoolean(value.toString());
        }
        return booleanValue;
    }

    public void putAll(Map<? extends String, ? extends Object> m)
    {
        getCurrentPhaseMap().putAll(m);
    }

    public Object remove(Object key)
    {
        return getCurrentPhaseMap().remove(key);
    }

    public int size()
    {
        return getCurrentPhaseMap().size();
    }

    public Collection<Object> values()
    {
        return getCurrentPhaseMap().values();
    }

}
TOP

Related Classes of org.apache.myfaces.test.mock.MockFlash$MessageEntry

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.