Processing of Session Objects

Asked

Viewed 1,021 times

2

I’m working with legacy systems in JSF 1.2 and noticed that developers make absurd use of Session to store all kinds of objects, ranging from user-informed filters to collections.

As you can imagine, this is not handled properly. Many of the developers used parameters and other types of tricks to minimize the amount of things in session, but unfortunately none of them proposed any kind of standardization.

In conversation with some developers who work with me we have come to the conclusion that it’s past time to standardize this, so we decided to implement a Phaselistener that is able to identify where we are going, being responsible for eliminating unnecessary session objects.

So that our solution does not create problems, the objects will be stored in session with a marking indicating that it can be removed by our solution.

My question is this: Is this solution suitable? Is there a possibility that it will bring us some kind of inconvenience?

2 answers

2


Unfortunately many developers when working with JSF 1.2 log everything in. And a terrible practice.

Take a look at this post: http://balusc.blogspot.com.br/2007/03/post-redirect-get-pattern.html

It shows a technique of keeping objects during a request. At least it can give you an idea of the way to go.

Below I will post the code taken from there:

    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;

    import javax.faces.FacesException;
    import javax.faces.application.FacesMessage;
    import javax.faces.component.UIComponent;
    import javax.faces.component.UIInput;
    import javax.faces.component.UIViewRoot;
    import javax.faces.context.ExternalContext;
    import javax.faces.context.FacesContext;
    import javax.faces.event.PhaseEvent;
    import javax.faces.event.PhaseId;
    import javax.faces.event.PhaseListener;
    import javax.servlet.http.HttpServletRequest;

    /**
     * Implement the POST-Redirect-GET pattern for JSF.
     * <p>
     * This phaselistener is designed to be used for JSF 1.2 with request scoped beans of which its
     * facesmessages and input values should be retained in the new GET request. If you're using session
     * scoped beans only, then you can safely remove the <tt>saveUIInputValues()</tt> and
     * <tt>restoreUIInputValues()</tt> methods to save (little) performance. If you're using JSF 1.1,
     * then you can also remove the <tt>saveViewRoot()</tt> and <tt>restoreViewRoot</tt> methods,
     * because it is not needed with its view state saving system.
     *
     * @author BalusC
     * @link http://balusc.blogspot.com/2007/03/post-redirect-get-pattern.html
     */
    public class PostRedirectGetListener implements PhaseListener {

        // Init ---------------------------------------------------------------------------------------

        private static final String PRG_DONE_ID = "PostRedirectGetListener.postRedirectGetDone";
        private static final String SAVED_VIEW_ROOT_ID = "PostRedirectGetListener.savedViewRoot";
        private static final String ALL_FACES_MESSAGES_ID = "PostRedirectGetListener.allFacesMessages";
        private static final String ALL_UIINPUT_VALUES_ID = "PostRedirectGetListener.allUIInputValues";

        // Actions ------------------------------------------------------------------------------------

        /**
         * @see javax.faces.event.PhaseListener#getPhaseId()
         */
        public PhaseId getPhaseId() {

            // Only listen during the render response phase.
            return PhaseId.RENDER_RESPONSE;
        }

        /**
         * @see javax.faces.event.PhaseListener#beforePhase(javax.faces.event.PhaseEvent)
         */
        public void beforePhase(PhaseEvent event) {

            // Prepare.
            FacesContext facesContext = event.getFacesContext();
            ExternalContext externalContext = facesContext.getExternalContext();
            HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
            Map<String, Object> sessionMap = externalContext.getSessionMap();

            if ("POST".equals(request.getMethod())) {

                // Save viewroot, facesmessages and UIInput values from POST request in session so that
                // they'll be available on the subsequent GET request.
                saveViewRoot(facesContext);
                saveFacesMessages(facesContext);
                saveUIInputValues(facesContext);

                // Redirect POST request to GET request.
                redirect(facesContext);

                // Set the PRG toggle.
                sessionMap.put(PRG_DONE_ID, true);

            } else if (sessionMap.containsKey(PRG_DONE_ID)) {

                // Restore any viewroot, facesmessages and UIInput values in the GET request.
                restoreViewRoot(facesContext);
                restoreFacesMessages(facesContext);
                restoreUIInputValues(facesContext);

                // Remove the PRG toggle.
                sessionMap.remove(PRG_DONE_ID);
            }
        }

        /**
         * @see javax.faces.event.PhaseListener#afterPhase(javax.faces.event.PhaseEvent)
         */
        public void afterPhase(PhaseEvent event) {
            // Do nothing.
        }

        // Helpers ------------------------------------------------------------------------------------

        /**
         * Save the current viewroot of the given facescontext in session. This is important in JSF 1.2,
         * because the viewroot would be lost in the new GET request and will only be created during
         * the afterPhase of RENDER_RESPONSE. But as we need to restore the input values in the
         * beforePhase of RENDER_RESPONSE, we have to save and restore the viewroot first ourselves.
         * @param facesContext The involved facescontext.
         */
        private static void saveViewRoot(FacesContext facesContext) {
            UIViewRoot savedViewRoot = facesContext.getViewRoot();
            facesContext.getExternalContext().getSessionMap()
                    .put(SAVED_VIEW_ROOT_ID, savedViewRoot);
        }

        /**
         * Save all facesmessages of the given facescontext in session. This is done so because the
         * facesmessages are purely request scoped and would be lost in the new GET request otherwise.
         * @param facesContext The involved facescontext.
         */
        private static void saveFacesMessages(FacesContext facesContext) {

            // Prepare the facesmessages holder in the sessionmap. The LinkedHashMap has precedence over
            // HashMap, because in a LinkedHashMap the FacesMessages will be kept in order, which can be
            // very useful for certain error and focus handlings. Anyway, it's just your design choice.
            Map<String, List<FacesMessage>> allFacesMessages =
                    new LinkedHashMap<String, List<FacesMessage>>();
            facesContext.getExternalContext().getSessionMap()
                    .put(ALL_FACES_MESSAGES_ID, allFacesMessages);

            // Get client ID's of all components with facesmessages.
            Iterator<String> clientIdsWithMessages = facesContext.getClientIdsWithMessages();
            while (clientIdsWithMessages.hasNext()) {
                String clientIdWithMessage = clientIdsWithMessages.next();

                // Prepare client-specific facesmessages holder in the main facesmessages holder.
                List<FacesMessage> clientFacesMessages = new ArrayList<FacesMessage>();
                allFacesMessages.put(clientIdWithMessage, clientFacesMessages);

                // Get all messages from client and add them to the client-specific facesmessage list.
                Iterator<FacesMessage> facesMessages = facesContext.getMessages(clientIdWithMessage);
                while (facesMessages.hasNext()) {
                    clientFacesMessages.add(facesMessages.next());
                }
            }
        }

        /**
         * Save all input values of the given facescontext in session. This is done specific for request
         * scoped beans, because its properties would be lost in the new GET request otherwise.
         * @param facesContext The involved facescontext.
         */
        private static void saveUIInputValues(FacesContext facesContext) {

            // Prepare the input values holder in sessionmap.
            Map<String, Object> allUIInputValues = new HashMap<String, Object>();
            facesContext.getExternalContext().getSessionMap()
                    .put(ALL_UIINPUT_VALUES_ID, allUIInputValues);

            // Pass viewroot children to the recursive method which saves all input values.
            saveUIInputValues(facesContext, facesContext.getViewRoot().getChildren(), allUIInputValues);
        }

        /**
         * A recursive method to save all input values of the given facescontext in session.
         * @param facesContext The involved facescontext.
         */
        private static void saveUIInputValues(
                FacesContext facesContext, List<UIComponent> components, Map<String, Object> allUIInputValues)
        {
            // Walk through the components and if it is an instance of UIInput, then save the value.
            for (UIComponent component : components) {
                if (component instanceof UIInput) {
                    UIInput input = (UIInput) component;
                    allUIInputValues.put(input.getClientId(facesContext), input.getValue());
                }

                // Pass the children of the current component back to this recursive method.
                saveUIInputValues(facesContext, component.getChildren(), allUIInputValues);
            }
        }

        /**
         * Invoke a redirect to the same URL as the current action URL.
         * @param facesContext The involved facescontext.
         */
        private static void redirect(FacesContext facesContext) {

            // Obtain the action URL of the current view.
            String url = facesContext.getApplication().getViewHandler().getActionURL(
                    facesContext, facesContext.getViewRoot().getViewId());

            try {
                // Invoke a redirect to the action URL.
                facesContext.getExternalContext().redirect(url);
            } catch (IOException e) {
                // Uhh, something went seriously wrong.
                throw new FacesException("Cannot redirect to " + url + " due to IO exception.", e);
            }
        }

        /**
         * Restore any viewroot from session in the given facescontext.
         * @param facesContext The involved FacesContext.
         */
        private static void restoreViewRoot(FacesContext facesContext) {

            // Remove the saved viewroot from session.
            UIViewRoot savedViewRoot = (UIViewRoot)
                    facesContext.getExternalContext().getSessionMap().remove(SAVED_VIEW_ROOT_ID);

            // Restore it in the given facescontext.
            facesContext.setViewRoot(savedViewRoot);
        }

        /**
         * Restore any facesmessages from session in the given FacesContext.
         * @param facesContext The involved FacesContext.
         */
        @SuppressWarnings("unchecked")
        private static void restoreFacesMessages(FacesContext facesContext) {

            // Remove all facesmessages from session.
            Map<String, List<FacesMessage>> allFacesMessages = (Map<String, List<FacesMessage>>)
                    facesContext.getExternalContext().getSessionMap().remove(ALL_FACES_MESSAGES_ID);

            // Restore them in the given facescontext.
            for (Entry<String, List<FacesMessage>> entry : allFacesMessages.entrySet()) {
                for (FacesMessage clientFacesMessage : entry.getValue()) {
                    facesContext.addMessage(entry.getKey(), clientFacesMessage);
                }
            }
        }

        /**
         * Restore any input values from session in the given FacesContext.
         * @param facesContext The involved FacesContext.
         */
        @SuppressWarnings("unchecked")
        private static void restoreUIInputValues(FacesContext facesContext) {

            // Remove all input values from session.
            Map<String, Object> allUIInputValues = (Map<String, Object>)
                    facesContext.getExternalContext().getSessionMap().remove(ALL_UIINPUT_VALUES_ID);

            // Restore them in the given facescontext.
            for (Entry<String, Object> entry : allUIInputValues.entrySet()) {
                UIInput input = (UIInput) facesContext.getViewRoot().findComponent(entry.getKey());
                input.setValue(entry.getValue());
            }
        }

    }
  • Thank you very much! I have come to the conclusion that in fact a Phaselistener is the answer to my problems. I will adapt this solution and wait for some other answer here on the site. But thanks, I believe it is right there.

0

I think the ideal in your case would not be to create a "Garbage Collector" to eliminate unnecessary objects from the session. First because this solution would not be anything simple and second because objects should be placed in their correct scopes, being eliminated accordingly.

I recommend reviewing each case where an object is stored in the session and then using the appropriate scope for that: page, request, etc. Leaving in the session only what concerns it, such as user login information.

Search for different types of scope. You can start with this link and then look for examples and understand where each type of data should be. Surely your application will perform much better after this Refactoring, instead of adding more code yet.

  • Man, I know the scopes well and certainly the Viewscope would help a lot in most cases. Unfortunately JSF 1.2 only works with Request, Application and Session. All Beans used on this system are Request type.

  • Hmm... Sorry, I guess I was a little hasty. = P I only worked with JSF 2, so I didn’t know about this scope limitation. But I found this post here, who knows help in your case. = D

Browser other questions tagged

You are not signed in. Login or sign up in order to post.