'Spring Web Flow: add view state information to URL

I'm using spring web flow 2.5.1.RELEASE.

I need to show info about the current view state in the URL (I need it for customer journey tracking with google analytics).

Let's say I have a flow (named flow) with two view states (named one and two).

The format of the spring web flow generated URL is

PROTOCOL://DOMAIN/CONTEXT/SERVLET/FLOW?execution=e?s?

ie) https://example.com/project/spring/flow?execution=e1s2

I need the URL to be

https://example.com/project/spring/flow/one?execution=e1s2

or

https://example.com/project/spring/flow?execution=e1s2&viewState=one

when the flow is in view state one and I need it to be

https://example.com/project/spring/flow/two?execution=e1s2

or

https://example.com/project/spring/flow?execution=e1s2&viewState=two

when the flow is in view state two.

Any help will be greatly appreciated.

Regards



Solution 1:[1]

After some research and trial and error I managed to find a solution, it's a little bit complicated but ... hope someone find it useful ...

1 .- Extend DefaultFlowUrlHandler, override the method getFlowId to deal with custom URLs and configure it

@Bean
public FlowHandlerMapping flowHandlerMapping() {
    FlowHandlerMapping mapping = new FlowHandlerMapping();
    mapping.setFlowUrlHandler(customFlowUrlHandlerForFlowHandlerMapping());
    ...

2 .- Extend DefaultFlowUrlHandler, override the methods getFlowExecutionKey (to extract executionKey from cookie) and createFlowExecutionUrl (to set request attribute executionKey and return custom URL) and configure it

@Bean
public FlowHandlerAdapter flowHandlerAdapter() {
    JsfFlowHandlerAdapter adapter = new JsfFlowHandlerAdapter();
    adapter.setFlowExecutor(this.webFlowConfig.flowExecutor());
    adapter.setFlowUrlHandler(customFlowUrlHandlerForFlowHandlerAdapter());
    return adapter;
}

3 .- Create a filter that sets a CustomResponseWrapper that take the 'executionKey' attribute value from the request and set a cookie with that value.

4 .- As a side note, this technique can be used to get rid of the execution URL parameter.

5 .- Java code

public class FlowUrlhandlerForFlowHandlerMapping extends DefaultFlowUrlHandler /* CustomDefaultFlowUrlHandler */ {

    private final Logger logger = LoggerFactory.getLogger(FlowUrlhandlerForFlowHandlerMapping.class) ;

    // pathInfo: /landing-planes/oferta
    private String PATH_INFO_PREFIX = "/landing-planes/" ;

    /**
     * Return correct flowId for custom URL
     */
    @Override
    public String getFlowId(HttpServletRequest request) {
        String pathInfo = request.getPathInfo();
        logger.info("getFlowId - pathInfo: " + pathInfo) ;
        if (pathInfo != null) {
            if (pathInfo.startsWith(PATH_INFO_PREFIX)) {
                logger.info("getFlowId - Retornando landing-planes como flowId!!!");
                return "landing-planes" ;
            }
        }
        String flowId = super.getFlowId(request);
        logger.info("getFlowId(" + request + "): " + flowId);
        return flowId ;
    }

}
public class FlowUrlHandlerForFlowHandlerAdapter extends DefaultFlowUrlHandler /* CustomDefaultFlowUrlHandler */ {


    private final Logger logger = LoggerFactory.getLogger(FlowUrlHandlerForFlowHandlerAdapter.class) ;

    // pathInfo: /landing-planes/oferta
    private String PATH_INFO_PREFIX = "/landing-planes/" ;

    private static final String PATH_INFO_FLOW_INIT = "/landing-planes/init";

    /**
     * Extract and return flowExecutionKey from cookie.
     */
    @Override
    public String getFlowExecutionKey(HttpServletRequest request) {
        String flowExecutionKey = super.getFlowExecutionKey(request); 
        logger.info("getFlowExecutionKey - super.getFlowExecutionKey: " + flowExecutionKey);

        String pathInfo = request.getPathInfo();
        logger.info("getFlowExecutionKey - pathInfo: " + pathInfo) ;

        if (!pathInfo.startsWith(PATH_INFO_PREFIX)) {
            return flowExecutionKey ;
        }

        if (pathInfo.equals(PATH_INFO_FLOW_INIT)) {
            return flowExecutionKey ;
        }

        Cookie cookieSwf = null;
        for (Cookie cookie : request.getCookies()) {
            if (cookie.getName().equals("swf")) {
                cookieSwf = cookie ;
            }
        }
        if (cookieSwf != null) {
            String value =  cookieSwf.getValue() ;
            logger.warn("El valor de la cookie es " + value) ;
            if (value == null) {
                logger.warn("El valor de la cookie es null") ;
            } else {
                String fek = value ;
                logger.info("fek: " + fek);
                flowExecutionKey = fek ;
            }
        }
        return flowExecutionKey ;
    }

    /**
     * If custom url, set request attribute 'executionKey' and return new custom url (indicated by application logic in 'view' attribute of the request)
     */
    @Override
    public String createFlowExecutionUrl(String flowId, String flowExecutionKey, HttpServletRequest request) {
        String flowExecutionUrl = super.createFlowExecutionUrl(flowId, flowExecutionKey, request);
        // /suscripciones-ventas/spring/landing-planes/oferta?execution=e2s2
        logger.info("createFlowExecutionUrl(" + flowId + ", " + flowExecutionKey + ", request): " + flowExecutionUrl);

        String pathInfo = request.getPathInfo();
        logger.info("createFlowExecutionUrl - pathInfo: " + pathInfo) ;
        // /suscripciones-ventas/spring/landing-planes/init


        if (pathInfo.startsWith(PATH_INFO_PREFIX)) {
            String vista = (String) request.getAttribute("view") ;
            logger.info("vista: " + vista);
            if (vista == null) {
                vista = pathInfo.replace(PATH_INFO_PREFIX, "") ;
                logger.info("vista tomada del path info: " + vista);
            }
            return procesarUrl(flowExecutionUrl, request, "/suscripciones-ventas/spring/landing-planes/" + vista) ;
        }

        return flowExecutionUrl ;
    }

    private String procesarUrl(String flowExecutionUrl, HttpServletRequest request, String customUrl) {
        String executionKey = extractExecutionKey(flowExecutionUrl) ;
        logger.info("executionKey: " + executionKey);
        if (executionKey != null) {
            request.setAttribute("executionKey", executionKey);
        }
        logger.info("procesarUrl - customUrl: " + customUrl) ;
        return customUrl ;
    }

    private String extractExecutionKey(String string) {
        if (string == null) {
            logger.warn("El valor pasado como parametro es null") ;
        } else {
            int index = string.indexOf("?execution=") ;
            if (index == -1) {
                logger.warn("El valor pasado como parametro no contiene execution '" + string + "'") ; 
            } else {
                String fek = string.substring(index + "?execution=".length()) ;
                logger.info("fek: " + fek);
                return fek ;
            }
        }
        return null ;
    }

}
@Component
public class CustomSpringFilter extends GenericFilterBean {

    private static Logger logger = LoggerFactory.getLogger(CustomSpringFilter.class) ;

    private static String SWF_CUSTOM_URI_PREFIX = "/suscripciones-ventas/spring/landing-planes/" ;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {

        HttpServletRequest httpServletRequest = ((HttpServletRequest)request) ;
        String requestURI = httpServletRequest.getRequestURI() ;

        boolean processRequest = processRequest(requestURI) ;
        if (!processRequest) {
            chain.doFilter(request, response);
            return ;
        }

        logger.info("**************************");
        logger.info("requestURI: " + requestURI);
        if (requestURI.startsWith(SWF_CUSTOM_URI_PREFIX)) {
            HttpServletResponse httpServletResponse = ((HttpServletResponse)response) ;
            procesarRequest(chain, httpServletRequest, httpServletResponse);
            return;
        }

        chain.doFilter(request, response);

    }

    private String[] ignoreExtensionArray = {".js", ".css", ".jpg", ".png"} ;

    private boolean processRequest(String requestURI) {
        for (int i = 0; i < ignoreExtensionArray.length; i++) {
            if (requestURI.endsWith(ignoreExtensionArray[i])) {
                return false ;
            }
        }
        if (requestURI.endsWith("pase/onLogin")) {
            return false ;
        }
        return true ;
    }

    private void procesarRequest(
        FilterChain filterchain, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse
    ) throws IOException, ServletException {
        CustomResponseWrapper responseWrapper = new CustomResponseWrapper(httpServletResponse, httpServletRequest);
        filterchain.doFilter(httpServletRequest, responseWrapper);
    }

}
public class CustomResponseWrapper extends HttpServletResponseWrapper {

    private static Logger logger = LoggerFactory.getLogger(CustomResponseWrapper.class) ;

    private HttpServletRequest httpServletRequest ;

    private boolean checked = false ;

    public CustomResponseWrapper(HttpServletResponse response, HttpServletRequest httpServletRequest) {
        super(response);
        this.httpServletRequest = httpServletRequest ;
    }

    @Override
    public void sendRedirect(String location) throws IOException {
        logger.info("sendRedirect: " + location);
        checkAttributeAndSetCookie();
        super.sendRedirect(location);
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        checkAttributeAndSetCookie();
        return super.getWriter();
    }   

    private void checkAttributeAndSetCookie() {
        if (checked) {
            return ;
        }
        checked = true ;
        String executionKey = (String) httpServletRequest.getAttribute("executionKey");
        logger.info("executionKey: " + executionKey);
        if (executionKey != null) {
            Cookie cookie = new Cookie("swf", executionKey) ;
            super.addCookie(cookie);
        }
    }

}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 user68459