org.openmrs.module.uiframework.FragmentActionController.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.module.uiframework.FragmentActionController.java

Source

/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.module.uiframework;

import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.openmrs.api.APIAuthenticationException;
import org.openmrs.api.AdministrationService;
import org.openmrs.api.context.Context;
import org.openmrs.api.context.ContextAuthenticationException;
import org.openmrs.ui.framework.FormatterImpl;
import org.openmrs.ui.framework.SimpleObject;
import org.openmrs.ui.framework.UiFrameworkException;
import org.openmrs.ui.framework.UiFrameworkUtil;
import org.openmrs.ui.framework.ViewException;
import org.openmrs.ui.framework.WebConstants;
import org.openmrs.ui.framework.fragment.FragmentFactory;
import org.openmrs.ui.framework.fragment.action.FailureResult;
import org.openmrs.ui.framework.fragment.action.ObjectResult;
import org.openmrs.ui.framework.fragment.action.SuccessResult;
import org.openmrs.ui.util.ExceptionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.StringTokenizer;

/**
 * Lets clients access pages via:
 * <ul>
 *    <li>(in 1.8.4+) .../openmrs/provider/subfolder/fragmentName/methodName.action</li>
 *  <li>(in 1.8-1.8.3) .../openmrs/action/subfolder/fragmentName/methodName.form</li>
 * </ul>
 */
@Controller
public class FragmentActionController {

    private final Logger log = LoggerFactory.getLogger(FragmentActionController.class);

    public final static String SHOW_HTML_VIEW = "/module/uiframework/showHtml";

    @Autowired
    @Qualifier("coreFragmentFactory")
    FragmentFactory fragmentFactory;

    @Autowired
    MessageSource messageSource;

    @Autowired
    @Qualifier("adminService")
    AdministrationService administrationService;

    @RequestMapping("/action/**")
    public String handleUrlStartingWithAction(
            @RequestParam(value = "returnFormat", required = false) String returnFormat,
            @RequestParam(value = "successUrl", required = false) String successUrl,
            @RequestParam(value = "failureUrl", required = false) String failureUrl, HttpServletRequest request,
            Model model, HttpServletResponse response) throws Exception {
        // everything after the contextPath, e.g. "/action/emr/registration/checkin.form"
        String path = request.getServletPath();
        path = path.substring("/action/".length(), path.lastIndexOf("."));
        return handlePath(path, returnFormat, successUrl, failureUrl, request, model, response);
    }

    @RequestMapping("**/*.action")
    public String handleUrlWithDotAction(
            @RequestParam(value = "returnFormat", required = false) String returnFormat,
            @RequestParam(value = "successUrl", required = false) String successUrl,
            @RequestParam(value = "failureUrl", required = false) String failureUrl, HttpServletRequest request,
            Model model, HttpServletResponse response) throws Exception {
        // everything after the contextPath, e.g. "/emr/registration/checkin.action"
        String path = request.getServletPath();
        path = path.substring(1, path.lastIndexOf(".action"));
        return handlePath(path, returnFormat, successUrl, failureUrl, request, model, response);
    }

    /**
     * @param path should be of the form "provider/optional/subdirectories/fragmentName/actionName"
     * @param returnFormat
     * @param successUrl
     * @param failureUrl
     * @param request
     * @param model
     * @param response
     * @return
     * @throws Exception
     */
    public String handlePath(String path, String returnFormat, String successUrl, String failureUrl,
            HttpServletRequest request, Model model, HttpServletResponse response) throws Exception {
        // handle the case where the url has two slashes, e.g. host/openmrs//emr/radiology/order.action
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        int firstSlash = path.indexOf("/");
        int lastSlash = path.lastIndexOf("/");
        if (firstSlash < 0 || lastSlash < 0) {
            throw new IllegalArgumentException(
                    "fragment action request must have at least provider/fragmentName/actionName, but this does not: "
                            + request.getRequestURI());
        }
        String providerName = path.substring(0, firstSlash);
        String fragmentName = path.substring(firstSlash + 1, lastSlash);
        String action = path.substring(lastSlash + 1);

        if (returnFormat == null) {
            String acceptHeader = request.getHeader("Accept");
            if (StringUtils.isNotEmpty(acceptHeader)) {
                if (acceptHeader.startsWith("application/json")) {
                    returnFormat = "json";
                }
            }
        }

        Object resultObject;
        try {
            resultObject = fragmentFactory.invokeFragmentAction(providerName, fragmentName, action, request);

            // Default status code for failures is 400 (BAD REQUEST), successes is 200 (OK)
            response.setStatus((resultObject instanceof FailureResult) ? 400 : 200);

        } catch (Exception ex) {

            // Look for specific exceptions down the chain
            Exception specificEx = null;

            // It's possible that the underlying exception is that the user was logged out or lacks privileges
            if ((specificEx = ExceptionUtil.findExceptionInChain(ex, APIAuthenticationException.class)) != null) {
                resultObject = new FailureResult("#APIAuthenticationException#" + specificEx.getMessage());
                response.setStatus(403); // 403 (FORBIDDEN)
            } else if ((specificEx = ExceptionUtil.findExceptionInChain(ex,
                    ContextAuthenticationException.class)) != null) {
                resultObject = new FailureResult("#APIAuthenticationException#" + specificEx.getMessage());
                response.setStatus(403); // 403 (FORBIDDEN)
            } else {
                // We don't know how to handle other types of exceptions
                log.error("error", ex);

                // TODO figure how to return UiFrameworkExceptions that result from
                // missing controllers or methods with status 404 (NOT FOUND)
                throw new UiFrameworkException("Error invoking fragment action", ex);
            }
        }

        if (!StringUtils.isEmpty(returnFormat)) {
            // this is an ajax request, so we need to return an object

            // turn the non-object result types into ObjectResults
            if (resultObject == null) {
                resultObject = new SuccessResult();
            } else if (resultObject instanceof SuccessResult) {
                SuccessResult success = (SuccessResult) resultObject;
                resultObject = SimpleObject.create("success", "true", "message", success.getMessage());
            } else if (resultObject instanceof FailureResult) {
                FailureResult failure = (FailureResult) resultObject;
                resultObject = SimpleObject.create("failure", "true", "globalErrors", failure.getGlobalErrors(),
                        "fieldErrors", failure.getFieldErrorMap());
            } else if (resultObject instanceof ObjectResult) {
                resultObject = ((ObjectResult) resultObject).getWrapped();
            }

            // Convert result to JSON or plain text depending on requested format
            Object result;
            if (returnFormat.equals("json")) {
                result = toJson(resultObject);
            } else {
                result = resultObject.toString();
            }

            model.addAttribute("html", result);

            return SHOW_HTML_VIEW;

        } else {
            // this is a regular post, so we will return a page

            if (successUrl == null)
                successUrl = getSuccessUrl(request);
            if (failureUrl == null)
                failureUrl = getFailureUrl(request, successUrl);

            successUrl = removeContextPath(successUrl);
            failureUrl = removeContextPath(failureUrl);

            if (resultObject == null || resultObject instanceof SuccessResult) {
                if (resultObject != null) {
                    SuccessResult result = (SuccessResult) resultObject;
                    if (result.getMessage() != null)
                        request.getSession().setAttribute(WebConstants.OPENMRS_MSG_ATTR, result.getMessage());
                }
                return "redirect:/" + successUrl;
            } else if (resultObject instanceof FailureResult) {
                // TODO harmonize this with the return-type version
                FailureResult failureResult = (FailureResult) resultObject;
                String errorMessage = null;
                if (failureResult.getSingleError() != null) {
                    errorMessage = failureResult.getSingleError();
                } else if (failureResult.getErrors() != null) {
                    Errors errors = failureResult.getErrors();
                    StringBuilder sb = new StringBuilder();
                    sb.append("<ul>");
                    for (ObjectError err : errors.getGlobalErrors()) {
                        sb.append("<li>" + UiFrameworkUtil.getMessage(err) + "</li>");
                    }
                    for (FieldError err : errors.getFieldErrors()) {
                        sb.append("<li>" + err.getField() + ": " + UiFrameworkUtil.getMessage(err) + "</li>");
                    }
                    sb.append("</ul>");
                    errorMessage = sb.toString();
                }
                request.getSession().setAttribute(WebConstants.OPENMRS_ERROR_ATTR, errorMessage);
                return redirectHelper(failureUrl, model);
            } else if (resultObject instanceof ObjectResult) {
                // the best we can do is just display a formatted version of the wrapped object
                String formatted = new FormatterImpl(messageSource, administrationService)
                        .format(((ObjectResult) resultObject).getWrapped(), Context.getLocale());
                model.addAttribute("html", formatted);
                return SHOW_HTML_VIEW;

            } else {
                throw new RuntimeException(
                        "Don't know how to handle fragment action result type: " + resultObject.getClass());
            }
        }
    }

    private String removeContextPath(String url) {
        if (url != null) {
            if (url.startsWith(WebConstants.CONTEXT_PATH))
                url = url.substring(WebConstants.CONTEXT_PATH.length());
            else if (url.startsWith("/" + WebConstants.CONTEXT_PATH))
                url = url.substring(WebConstants.CONTEXT_PATH.length() + 1);
        }
        return url;
    }

    /**
     * Exposes any query parameter from url
     * 
     * @param urlString
     * @param model
     * @throws MalformedURLException
     */
    private String redirectHelper(String urlString, Model model) throws MalformedURLException {
        URL url = new URL(urlString);
        if (url.getQuery() != null) {
            for (StringTokenizer st = new StringTokenizer(url.getQuery(), "&"); st.hasMoreTokens();) {
                String item = st.nextToken();
                int ind = item.indexOf('=');
                String name = item.substring(0, ind);
                String value = URLDecoder.decode(item.substring(ind + 1));
                model.addAttribute(name, value);
            }
        }
        return "redirect:" + removeContextPath(url.getPath());
    }

    private String getSuccessUrl(HttpServletRequest request) {
        String referrer = request.getHeader("referer");
        if (referrer != null) {
            try {
                referrer = new URL(referrer).getFile();
            } catch (Exception ex) {
                throw new RuntimeException("Couldn't get file part of referer", ex);
            }
            return referrer;
        } else {
            throw new RuntimeException("Don't know what to use as success url");
        }
    }

    private String getFailureUrl(HttpServletRequest request, String successUrl) {
        String referrer = request.getHeader("referer");
        if (referrer != null) {
            try {
                referrer = new URL(referrer).getFile();
            } catch (Exception ex) {
                throw new RuntimeException("Couldn't get file part of referer", ex);
            }
            return referrer;
        } else {
            return successUrl;
        }
    }

    private String toJson(Object object) {
        try {
            if (object instanceof ObjectResult) {
                object = ((ObjectResult) object).getWrapped();
            }
            ObjectMapper mapper = new ObjectMapper();
            StringWriter sw = new StringWriter();
            mapper.writeValue(sw, object);
            return sw.toString();
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new ViewException("Error generating JSON", ex);
        }
    }
}