Handling Objects in Session

2

I'm working with legacy systems in JSF 1.2 and noticed that developers make an absurd use of the session to store all kinds of objects, ranging from user-supplied filters to collections.

As you can imagine, this is not treated properly. Many of the developers used parameters and other types of artifacts 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 is past time to standardize this, so we have decided to implement a PhaseListener that is able to identify where we are going, being responsible for eliminating unnecessary objects from the session.

In order for our solution to not create problems, objects will be stored in session with a mark indicating that it can be removed by our solution.

My question is this: Is this solution adequate? Is it possible that it brings us some kind of inconvenience?

    
asked by anonymous 31.01.2014 / 14:24

2 answers

2

Unfortunately, many developers working with JSF 1.2 put everything in session. It's a terrible practice.

Take a look at this post: link

It shows a technique of holding objects during a request. At least it can give you an idea of the way forward.

Below I'll 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());
            }
        }

    }
    
31.01.2014 / 16:27
0

I think the ideal thing in your case would not be to create a "Garbage Collector" to eliminate unnecessary objects from the session. First of all, this solution would not be 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 use 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 search 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.

    
31.01.2014 / 14:49