org.motechproject.server.decisiontree.web.VxmlController.java Source code

Java tutorial

Introduction

Here is the source code for org.motechproject.server.decisiontree.web.VxmlController.java

Source

/**
 * MOTECH PLATFORM OPENSOURCE LICENSE AGREEMENT
 *
 * Copyright (c) 2011 Grameen Foundation USA.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. Neither the name of Grameen Foundation USA, nor its respective contributors
 * may be used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GRAMEEN FOUNDATION USA AND ITS CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL GRAMEEN FOUNDATION USA OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */
package org.motechproject.server.decisiontree.web;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.motechproject.decisiontree.model.Node;
import org.motechproject.decisiontree.model.Transition;
import org.motechproject.server.decisiontree.TreeNodeLocator;
import org.motechproject.server.decisiontree.service.DecisionTreeService;
import org.motechproject.server.decisiontree.service.TreeEventProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;

/**
 * Spring MVC controller implementation provides method to handle HTTP requests and generate
 * VXML documents based on a Decision Tree Node model object and the corresponding Velocity template
 *
 * @author Igor (iopushnyev@2paths.com)
 */
public class VxmlController extends MultiActionController {

    private Logger logger = LoggerFactory.getLogger((this.getClass()));

    public static final String TREE_NAME_PARAM = "tNm";
    public static final String TRANSITION_KEY_PARAM = "trK";
    public static final String TRANSITION_PATH_PARAM = "trP";
    public static final String PATIENT_ID_PARAM = "pId";
    public static final String LANGUAGE_PARAM = "ln";

    public static final String NODE_TEMPLATE_NAME = "node";
    public static final String LEAF_TEMPLATE_NAME = "leaf";
    public static final String ERROR_MESSAGE_TEMPLATE_NAME = "node_error";
    public static final String EXIT_TEMPLATE_NAME = "exit";

    public static final String TREE_NAME_SEPARATOR = ",";

    @Autowired
    DecisionTreeService decisionTreeService;

    @Autowired
    TreeEventProcessor treeEventProcessor;

    enum Errors {
        NULL_PATIENTID_LANGUAGE_OR_TREENAME_PARAM, NULL_TRANSITION_PATH_PARAM, NULL_DESTINATION_NODE, INVALID_TRANSITION_KEY, INVALID_TRANSITION_KEY_TYPE, GET_NODE_ERROR
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> convertParams(@SuppressWarnings("rawtypes") Map requestParams) {
        Map<String, Object> params = new HashMap<String, Object>();
        Assert.notNull(requestParams);
        for (Map.Entry<String, String[]> e : (Set<Map.Entry<String, String[]>>) requestParams.entrySet()) {
            if (e.getValue().length == 1) {
                params.put(e.getKey(), e.getValue()[0]);
            } else {
                params.put(e.getKey(), e.getValue());
            }
        }
        return params;
    }

    /**
     * Handles Decision Tree Node HTTP requests and generates a VXML document based on a Velocity template.
     * The HTTP request should contain the Tree ID, Node ID, Patient ID and Selected Transition Key (optional) parameters
     *
     */
    public ModelAndView node(HttpServletRequest request, HttpServletResponse response) {
        logger.info("Generating decision tree node VXML");

        response.setContentType("text/plain");
        response.setCharacterEncoding("UTF-8");

        Node node = null;
        String transitionPath = null;
        Map<String, Object> params = convertParams(request.getParameterMap());

        String patientId = request.getParameter(PATIENT_ID_PARAM);
        String language = request.getParameter(LANGUAGE_PARAM);
        String treeNameString = request.getParameter(TREE_NAME_PARAM);
        String encodedParentTransitionPath = request.getParameter(TRANSITION_PATH_PARAM);
        String transitionKey = request.getParameter(TRANSITION_KEY_PARAM);

        logger.info(" Node HTTP  request parameters: " + PATIENT_ID_PARAM + ": " + patientId + ", " + LANGUAGE_PARAM
                + ": " + language + ", " + TREE_NAME_PARAM + ": " + treeNameString + ", " + TRANSITION_PATH_PARAM
                + ": " + encodedParentTransitionPath + ", " + TRANSITION_KEY_PARAM + ": " + transitionKey);

        if (StringUtils.isBlank(patientId) || StringUtils.isBlank(language)
                || StringUtils.isBlank(treeNameString)) {

            logger.error("Invalid HTTP request - the following parameters: " + PATIENT_ID_PARAM + ", "
                    + LANGUAGE_PARAM + " and " + TREE_NAME_PARAM + " are mandatory");
            return getErrorModelAndView(Errors.NULL_PATIENTID_LANGUAGE_OR_TREENAME_PARAM);
        }

        String[] treeNames = treeNameString.split(TREE_NAME_SEPARATOR);
        String currentTree = treeNames[0];
        // put only one tree name in params
        params.put(TREE_NAME_PARAM, currentTree);

        if (transitionKey == null) { // get root node
            try {
                String rootTransitionPath = TreeNodeLocator.PATH_DELIMITER;
                node = decisionTreeService.getNode(currentTree, rootTransitionPath);
                transitionPath = rootTransitionPath;
            } catch (Exception e) {
                logger.error("Can not get node by Tree Name: " + currentTree + " transition path: " + patientId, e);
            }
        } else { // get not root node
            String parentTransitionPath = null;
            try {
                if (encodedParentTransitionPath == null) {

                    logger.error(
                            "Invalid HTTP request - the  " + TRANSITION_PATH_PARAM + " parameter is mandatory");
                    return getErrorModelAndView(Errors.NULL_TRANSITION_PATH_PARAM);
                }

                parentTransitionPath = new String(Base64.decodeBase64(encodedParentTransitionPath));
                Node parentNode = decisionTreeService.getNode(currentTree, parentTransitionPath);

                Transition transition = parentNode.getTransitions().get(transitionKey);

                if (transition == null) {
                    logger.error("Invalid Transition Key. There is no transition with key: " + transitionKey
                            + " in the Node: " + parentNode);
                    return getErrorModelAndView(Errors.INVALID_TRANSITION_KEY);
                }

                treeEventProcessor.sendActionsAfter(parentNode, parentTransitionPath, params);

                treeEventProcessor.sendTransitionActions(transition, params);

                node = transition.getDestinationNode();

                if (node == null || (node.getPrompts().isEmpty() && node.getActionsAfter().isEmpty()
                        && node.getActionsBefore().isEmpty() && node.getTransitions().isEmpty())) {
                    if (treeNames.length > 1) {
                        //reduce the current tree and redirect to the next tree
                        treeNames = (String[]) ArrayUtils.remove(treeNames, 0);
                        String view = String.format(
                                "redirect:/tree/vxml/node?" + PATIENT_ID_PARAM + "=%s&" + TREE_NAME_PARAM + "=%s&"
                                        + LANGUAGE_PARAM + "=%s",
                                patientId, StringUtils.join(treeNames, TREE_NAME_SEPARATOR), language);
                        ModelAndView mav = new ModelAndView(view);
                        return mav;
                    } else {
                        //TODO: Add support for return url
                        ModelAndView mav = new ModelAndView(EXIT_TEMPLATE_NAME);
                        return mav;
                    }

                } else {
                    transitionPath = parentTransitionPath
                            + (TreeNodeLocator.PATH_DELIMITER.equals(parentTransitionPath) ? ""
                                    : TreeNodeLocator.PATH_DELIMITER)
                            + transitionKey;
                }

            } catch (Exception e) {
                logger.error("Can not get node by Tree ID : " + currentTree + " and Transition Path: "
                        + parentTransitionPath, e);
            }
        }

        if (node != null) {

            //validate node
            for (Map.Entry<String, Transition> transitionEntry : node.getTransitions().entrySet()) {

                try {
                    Integer.parseInt(transitionEntry.getKey());
                } catch (NumberFormatException e) {
                    logger.error("Invalid node: " + node
                            + "\n In order  to be used in VXML transition keys should be an Integer");
                    return getErrorModelAndView(Errors.INVALID_TRANSITION_KEY_TYPE);
                }

                Transition transition = transitionEntry.getValue();
                if (transition.getDestinationNode() == null) {
                    logger.error(
                            "Invalid node: " + node + "\n Null Destination Node in the Transition: " + transition);
                    return getErrorModelAndView(Errors.NULL_DESTINATION_NODE);
                }
            }

            treeEventProcessor.sendActionsBefore(node, transitionPath, params);

            ModelAndView mav = new ModelAndView();
            if (node.getTransitions().size() > 0) {
                mav.setViewName(NODE_TEMPLATE_NAME);
                mav.addObject("treeName", treeNameString);
            } else { // leaf
                //reduce the current tree and redirect to the next tree
                treeNames = (String[]) ArrayUtils.remove(treeNames, 0);
                mav.setViewName(LEAF_TEMPLATE_NAME);
                mav.addObject("treeName", StringUtils.join(treeNames, TREE_NAME_SEPARATOR));
            }
            mav.addObject("contentPath", request.getContextPath());
            mav.addObject("node", node);
            mav.addObject("patientId", patientId);
            mav.addObject("language", language);
            mav.addObject("transitionPath", Base64.encodeBase64URLSafeString(transitionPath.getBytes()));
            mav.addObject("escape", new StringEscapeUtils());

            return mav;
        } else {
            return getErrorModelAndView(Errors.GET_NODE_ERROR);
        }

    }

    private ModelAndView getErrorModelAndView(Errors errorCode) {

        ModelAndView mav = new ModelAndView();
        mav.setViewName(ERROR_MESSAGE_TEMPLATE_NAME);
        mav.addObject("errorCode", errorCode);
        return mav;
    }

}