org.silverpeas.processmanager.servlets.SessionSafeFunctionHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.silverpeas.processmanager.servlets.SessionSafeFunctionHandler.java

Source

/*
 * Copyright (C) 2000 - 2018 Silverpeas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * As a special exception to the terms and conditions of version 3.0 of
 * the GPL, you may redistribute this Program in connection with Free/Libre
 * Open Source Software ("FLOSS") applications as described in Silverpeas's
 * FLOSS exception. You should have recieved a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * "http://www.silverpeas.org/docs/core/legal/floss_exception.html"
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package org.silverpeas.processmanager.servlets;

import org.apache.commons.fileupload.FileItem;
import org.silverpeas.core.exception.UtilException;
import org.silverpeas.core.util.StringUtil;
import org.silverpeas.core.util.file.FileUploadUtil;
import org.silverpeas.core.util.logging.SilverLogger;
import org.silverpeas.core.web.http.HttpRequest;
import org.silverpeas.processmanager.ProcessManagerException;
import org.silverpeas.processmanager.ProcessManagerSessionController;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * A SessionSafeFunctionHandler must be used to prevent conflicts in HTTP Session when user
 * navigate
 * using several windows sharing the same session.
 * @author Ludovic Bertin
 */
public abstract class SessionSafeFunctionHandler implements FunctionHandler {

    private static final String PROCESS_MANAGERTOKEN_ID = "processManagertokenId";
    private static final String CANCEL_PARAMETER = "cancel";

    @Override
    public final String getDestination(String function, ProcessManagerSessionController session,
            HttpServletRequest req) throws ProcessManagerException {
        HttpRequest request = HttpRequest.decorate(req);
        List<FileItem> items = null;

        // Retrieves current token Id
        String currentTokenId = session.getCurrentTokenId();

        // Retrieves items in case of multipart request
        if (request.isContentInMultipart()) {
            try {
                items = new ArrayList<>();
                if (request.getAttribute("ALREADY_PROCESSED") == null) {
                    items.addAll(request.getFileItems());
                    request.setAttribute("ALREADY_PROCESSED", true);
                }
            } catch (UtilException e) {
                SilverLogger.getLogger(this).error(e);
            }
        }

        // if multiple windows within the same session, user is redirected to an explicit error page
        if (!checkTokenId(session, currentTokenId, request, items)) {
            return "/processManager/jsp/multiWindowDetected.jsp";
        }

        // No weird usage detected, let's the handler do its stuff.
        return computeDestination(function, session, request, items);
    }

    /**
     * Checks if a parameter named "processManagertokenId" is present in request and equal to given
     * tokenId.
     * @param session ProcessManager Session Controller
     * @param currentTokenId the current token Id to compare to parameter's value
     * @param request the HTTP request
     * @param items FileItems list to be completed if request is multipart
     * @return false is parameter is not present or different from given token id
     */
    private boolean checkTokenId(ProcessManagerSessionController session, String currentTokenId,
            HttpRequest request, List<FileItem> items) {

        String givenTokenId;
        boolean isCancellation;

        // multipart form submission.
        if ((request.isContentInMultipart()) && (items != null)) {
            String cancel = FileUploadUtil.getParameter(items, CANCEL_PARAMETER);
            isCancellation = StringUtil.getBooleanValue(cancel);

            // retrieve given token id
            givenTokenId = FileUploadUtil.getParameter(items, PROCESS_MANAGERTOKEN_ID);
        } else {
            // simple form submission
            // in case of creation cancellation, test must be bypassed and current tolen id resetted
            String cancel = request.getParameter(CANCEL_PARAMETER);
            isCancellation = StringUtil.getBooleanValue(cancel);

            // compare given token id with current token id
            givenTokenId = request.getParameter(PROCESS_MANAGERTOKEN_ID);
        }

        if (isCancellation) {
            resetTokenId(session, request);
        }

        return doVerifications(currentTokenId, givenTokenId, isCancellation);
    }

    /**
     * Checks for incorrect usages :
     * <p>
     * CASE 1 :         no conflict detection
     * scenario :       user cancels a current action (from action's form)
     * how to detect :  a parameter named "cancel" is present in request and equals to true
     * <p>
     * CASE 2 :         conflict detection
     * scenario :       an action is being processed, user open another window and try to access same
     * workflow
     * how to detect :  no token ID present in request, but there's a current token ID in session
     * controller
     * <p>
     * CASE 3 :         no conflict detection
     * scenario :       user submits action form correctly
     * how to detect :  a token ID is present in request and equal to current token ID stored in
     * session controller
     * <p>
     * CASE 4 :         conflict detection
     * scenario :       an action is being processed, user open another window and logged in =>
     * previous Silverpeas session data is lost, then user open a instance procedure from same
     * workflow and at least try submit action form from the first window
     * how to detect :  a token ID is present in request but different from current token ID stored
     * in
     * session controller
     * <p>
     * CASE 5 :         conflict detection
     * scenario :       an action is being processed, user open another window and logged in =>
     * previous Silverpeas session data is lost then try submit action form from the first window
     * how to detect :  a token ID is present in request but no current token ID stored in session
     * controller
     * <p>
     * CASE 6 :         no conflict detection
     * scenario :       user navigates in only one window, or in several windows but only in
     * read-only
     * uses cases
     * how to detect :  no current token ID in session controller and no token ID present in request
     * @param currentTokenId the current token identifier
     * @param givenTokenId the givent token identifier
     * @param isCancellation is cancellation
     * @return
     */
    private boolean doVerifications(String currentTokenId, String givenTokenId, boolean isCancellation) {
        // CASE 1 : user cancels a current action (from action's form)
        if (isCancellation) {
            return true;
        } else if (StringUtil.isDefined(currentTokenId)) {

            // CASE 2 : an action is being processed, user open another window and try to access same
            // workflow
            if (!StringUtil.isDefined(givenTokenId)) {
                SilverLogger.getLogger(this)
                        .error("Provided token empty! "
                                + "CASE 2: an action is being processed, user open another window and try to access"
                                + " same workflow");
                return false;
            } else {
                // CASE 3 : user submits action form correctly
                if (givenTokenId.equals(currentTokenId)) {
                    return true;
                }

                // CASE 4 :
                // an action is being processed, user open another window and logged in => previous
                // Silverpeas session data is lost
                // then user open a instance procedure from same workflow
                // and at least try submit action form from the first window
                else {
                    SilverLogger.getLogger(this).error("Bad token " + givenTokenId
                            + "CASE 4: an action is being processed, user open another window and logged in");
                    return false;
                }
            }
        } else {
            // CASE 5 : an action is being processed, user open another window and logged in =>
            // previous Silverpeas session data is lost then try submit action form from the first window
            if (StringUtil.isDefined(givenTokenId)) {
                SilverLogger.getLogger(this).error("No current token"
                        + "CASE 5: an action is being processed, user open another window and logged in");
                return false;
            }
            // CASE 6 : user navigates in only one window, or in several windows but only in read-only
            // uses cases
            else {
                return true;
            }
        }
    }

    /**
     * Generate random UUID and store it in request as attribute and in process manager session
     * controller
     * @param session the process manager session controller
     * @param request the http servlet request
     */
    protected void generateTokenId(ProcessManagerSessionController session, HttpServletRequest request) {
        // Generates and store new token id if needed
        UUID newTokenId = UUID.randomUUID();
        session.setCurrentTokenId(newTokenId.toString());
        request.setAttribute("currentTokenId", newTokenId.toString());
    }

    /**
     * Reset token Id.
     * @param session the process manager session controller
     * @param request the http servlet request
     */
    protected void resetTokenId(ProcessManagerSessionController session, HttpServletRequest request) {
        session.setCurrentTokenId(null);
        request.removeAttribute("currentTokenId");
    }

    /**
     * Main scenario to be implemented by handler.
     * @param function the name of use case to realize
     * @param session the process manager session controller
     * @param request the http servlet request
     * @param items eventual submitted items
     * @return the JSP servlet to be forwarded to.
     * @throws ProcessManagerException
     */
    protected abstract String computeDestination(String function, ProcessManagerSessionController session,
            HttpServletRequest request, List<FileItem> items) throws ProcessManagerException;

}