org.openmrs.module.web.controller.ModuleListController.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.module.web.controller.ModuleListController.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public License,
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
 *
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
 * graphic logo is a trademark of OpenMRS Inc.
 */
package org.openmrs.module.web.controller;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.api.APIAuthenticationException;
import org.openmrs.api.context.Context;
import org.openmrs.module.Module;
import org.openmrs.module.ModuleConstants;
import org.openmrs.module.ModuleException;
import org.openmrs.module.ModuleFactory;
import org.openmrs.module.ModuleFileParser;
import org.openmrs.module.ModuleUtil;
import org.openmrs.module.web.WebModuleUtil;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.util.PrivilegeConstants;
import org.openmrs.web.WebConstants;
import org.openmrs.web.WebUtil;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.view.RedirectView;

/**
 * Controller that backs the /admin/modules/modules.list page. This controller makes a list of
 * modules available and lets the user start, stop, and unload modules one at a time.
 */
public class ModuleListController extends SimpleFormController {

    /**
     * Logger for this class and subclasses
     */
    protected static final Log log = LogFactory.getLog(ModuleListController.class);

    /**
     * The onSubmit function receives the form/command object that was modified by the input form
     * and saves it to the db
     *
     * @see org.springframework.web.servlet.mvc.SimpleFormController#onSubmit(HttpServletRequest,
     *      HttpServletResponse, Object,
     *      BindException)
     */
    @Override
    protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command,
            BindException errors) throws Exception {

        if (!Context.hasPrivilege(PrivilegeConstants.MANAGE_MODULES)) {
            throw new APIAuthenticationException("Privilege required: " + PrivilegeConstants.MANAGE_MODULES);
        }

        HttpSession httpSession = request.getSession();
        String moduleId = ServletRequestUtils.getStringParameter(request, "moduleId", "");
        String view = getFormView();
        String success = "";
        String error = "";
        MessageSourceAccessor msa = getMessageSourceAccessor();

        String action = ServletRequestUtils.getStringParameter(request, "action", "");
        if (ServletRequestUtils.getStringParameter(request, "start.x", null) != null) {
            action = "start";
        } else if (ServletRequestUtils.getStringParameter(request, "stop.x", null) != null) {
            action = "stop";
        } else if (ServletRequestUtils.getStringParameter(request, "unload.x", null) != null) {
            action = "unload";
        }

        // handle module upload
        if ("upload".equals(action)) {
            // double check upload permissions
            if (!ModuleUtil.allowAdmin()) {
                error = msa.getMessage("Module.disallowUploads",
                        new String[] { ModuleConstants.RUNTIMEPROPERTY_ALLOW_ADMIN });
            } else {
                InputStream inputStream = null;
                File moduleFile = null;
                Module module = null;
                Boolean updateModule = ServletRequestUtils.getBooleanParameter(request, "update", false);
                Boolean downloadModule = ServletRequestUtils.getBooleanParameter(request, "download", false);
                List<Module> dependentModulesStopped = null;
                try {
                    if (downloadModule) {
                        String downloadURL = request.getParameter("downloadURL");
                        if (downloadURL == null) {
                            throw new MalformedURLException("Couldn't download module because no url was provided");
                        }
                        String fileName = downloadURL.substring(downloadURL.lastIndexOf("/") + 1);
                        final URL url = new URL(downloadURL);
                        inputStream = ModuleUtil.getURLStream(url);
                        moduleFile = ModuleUtil.insertModuleFile(inputStream, fileName);
                    } else if (request instanceof MultipartHttpServletRequest) {

                        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
                        MultipartFile multipartModuleFile = multipartRequest.getFile("moduleFile");
                        if (multipartModuleFile != null && !multipartModuleFile.isEmpty()) {
                            String filename = WebUtil.stripFilename(multipartModuleFile.getOriginalFilename());
                            // if user is using the "upload an update" form instead of the main form
                            if (updateModule) {
                                // parse the module so that we can get the id

                                Module tmpModule = new ModuleFileParser(multipartModuleFile.getInputStream())
                                        .parse();
                                Module existingModule = ModuleFactory.getModuleById(tmpModule.getModuleId());
                                if (existingModule != null) {
                                    dependentModulesStopped = ModuleFactory.stopModule(existingModule, false, true); // stop the module with these parameters so that mandatory modules can be upgraded

                                    for (Module depMod : dependentModulesStopped) {
                                        WebModuleUtil.stopModule(depMod, getServletContext());
                                    }

                                    WebModuleUtil.stopModule(existingModule, getServletContext());
                                    ModuleFactory.unloadModule(existingModule);
                                }
                                inputStream = new FileInputStream(tmpModule.getFile());
                                moduleFile = ModuleUtil.insertModuleFile(inputStream, filename); // copy the omod over to the repo folder
                            } else {
                                // not an update, or a download, just copy the module file right to the repo folder
                                inputStream = multipartModuleFile.getInputStream();
                                moduleFile = ModuleUtil.insertModuleFile(inputStream, filename);
                            }
                        }
                    }
                    module = ModuleFactory.loadModule(moduleFile);
                } catch (ModuleException me) {
                    log.warn("Unable to load and start module", me);
                    error = me.getMessage();
                } finally {
                    // clean up the module repository folder
                    try {
                        if (inputStream != null) {
                            inputStream.close();
                        }
                    } catch (IOException io) {
                        log.warn("Unable to close temporary input stream", io);
                    }

                    if (module == null && moduleFile != null) {
                        moduleFile.delete();
                    }
                }

                // if we didn't have trouble loading the module, start it
                if (module != null) {
                    ModuleFactory.startModule(module);
                    WebModuleUtil.startModule(module, getServletContext(), false);
                    if (module.isStarted()) {
                        success = msa.getMessage("Module.loadedAndStarted", new String[] { module.getName() });

                        if (updateModule && dependentModulesStopped != null) {
                            for (Module depMod : sortStartupOrder(dependentModulesStopped)) {
                                ModuleFactory.startModule(depMod);
                                WebModuleUtil.startModule(depMod, getServletContext(), false);
                            }
                        }

                    } else {
                        success = msa.getMessage("Module.loaded", new String[] { module.getName() });
                    }
                }
            }
        } else if ("".equals(moduleId)) {
            if (action.equals(msa.getMessage("Module.startAll"))) {
                boolean someModuleNeedsARefresh = false;
                Collection<Module> modules = ModuleFactory.getLoadedModules();
                Collection<Module> modulesInOrder = ModuleFactory.getModulesInStartupOrder(modules);
                for (Module module : modulesInOrder) {
                    if (ModuleFactory.isModuleStarted(module)) {
                        continue;
                    }

                    ModuleFactory.startModule(module);
                    boolean thisModuleCausesRefresh = WebModuleUtil.startModule(module, getServletContext(), true);
                    someModuleNeedsARefresh = someModuleNeedsARefresh || thisModuleCausesRefresh;
                }

                if (someModuleNeedsARefresh) {
                    WebModuleUtil.refreshWAC(getServletContext(), false, null);
                }
            } else {
                ModuleUtil.checkForModuleUpdates();
            }
        } else if (action.equals(msa.getMessage("Module.installUpdate"))) {
            // download and install update
            if (!ModuleUtil.allowAdmin()) {
                error = msa.getMessage("Module.disallowAdministration",
                        new String[] { ModuleConstants.RUNTIMEPROPERTY_ALLOW_ADMIN });
            }
            Module mod = ModuleFactory.getModuleById(moduleId);
            if (mod.getDownloadURL() != null) {
                ModuleFactory.stopModule(mod, false, true); // stop the module with these parameters so that mandatory modules can be upgraded
                WebModuleUtil.stopModule(mod, getServletContext());
                Module newModule = ModuleFactory.updateModule(mod);
                WebModuleUtil.startModule(newModule, getServletContext(), false);
            }
        } else { // moduleId is not empty
            if (!ModuleUtil.allowAdmin()) {
                error = msa.getMessage("Module.disallowAdministration",
                        new String[] { ModuleConstants.RUNTIMEPROPERTY_ALLOW_ADMIN });
            } else {
                log.debug("Module id: " + moduleId);
                Module mod = ModuleFactory.getModuleById(moduleId);

                // Argument to pass to the success/error message
                Object[] args = new Object[] { moduleId };

                if (mod == null) {
                    error = msa.getMessage("Module.invalid", args);
                } else {
                    if ("stop".equals(action)) {
                        mod.clearStartupError();
                        ModuleFactory.stopModule(mod);
                        WebModuleUtil.stopModule(mod, getServletContext());
                        success = msa.getMessage("Module.stopped", args);
                    } else if ("start".equals(action)) {
                        ModuleFactory.startModule(mod);
                        WebModuleUtil.startModule(mod, getServletContext(), false);
                        if (mod.isStarted()) {
                            success = msa.getMessage("Module.started", args);
                        } else {
                            error = msa.getMessage("Module.not.started", args);
                        }
                    } else if ("unload".equals(action)) {
                        if (ModuleFactory.isModuleStarted(mod)) {
                            ModuleFactory.stopModule(mod); // stop the module so that when the web stop is done properly
                            WebModuleUtil.stopModule(mod, getServletContext());
                        }
                        ModuleFactory.unloadModule(mod);
                        success = msa.getMessage("Module.unloaded", args);
                    }
                }
            }
        }

        view = getSuccessView();

        if (!"".equals(success)) {
            httpSession.setAttribute(WebConstants.OPENMRS_MSG_ATTR, success);
        }

        if (!"".equals(error)) {
            httpSession.setAttribute(WebConstants.OPENMRS_ERROR_ATTR, error);
        }

        return new ModelAndView(new RedirectView(view));
    }

    /**
     * @param modulesToStart
     * @return a new list, with the same elements as modulesToStart, sorted so that no module is before a module it depends on
     * @should sort modules correctly
     */
    List<Module> sortStartupOrder(List<Module> modulesToStart) {
        // can't use Collections.sort--we need a slower algorithm that guarantees to compare every pair of elements
        List<Module> candidates = new LinkedList<Module>(modulesToStart);
        List<Module> ret = new ArrayList<Module>();
        while (candidates.size() > 0) {
            Module mod = removeModuleWithNoDependencies(candidates);
            if (mod == null) {
                log.warn("Unable to determine suitable startup order for " + modulesToStart);
                return modulesToStart;
            }
            ret.add(mod);
        }
        return ret;
    }

    /**
     * Looks for a module in the list that doesn't depend on any other modules in the list.
     * If any is found, that module is removed from the list and returned.
     *
     * @param candidates
     * @return
     */
    private Module removeModuleWithNoDependencies(List<Module> candidates) {
        for (Iterator<Module> i = candidates.iterator(); i.hasNext();) {
            Module candidate = i.next();
            boolean suitable = true;
            for (Module other : candidates) {
                if (candidate.getRequiredModules().contains(other.getPackageName())) {
                    suitable = false;
                    break;
                }
            }
            if (suitable) {
                i.remove();
                return candidate;
            }
        }
        return null;
    }

    /**
     * This is called prior to displaying a form for the first time. It tells Spring the
     * form/command object to load into the request
     *
     * @see org.springframework.web.servlet.mvc.AbstractFormController#formBackingObject(HttpServletRequest)
     */
    @Override
    protected Object formBackingObject(HttpServletRequest request) throws ServletException {

        Collection<Module> modules = ModuleFactory.getLoadedModules();

        log.info("Returning " + modules.size() + " modules");

        return modules;
    }

    @Override
    protected Map<String, Object> referenceData(HttpServletRequest request) throws Exception {
        Map<String, Object> map = new HashMap<String, Object>();
        MessageSourceAccessor msa = getMessageSourceAccessor();

        map.put("allowAdmin", ModuleUtil.allowAdmin().toString());
        map.put("disallowUploads", msa.getMessage("Module.disallowUploads",
                new String[] { ModuleConstants.RUNTIMEPROPERTY_ALLOW_ADMIN }));

        map.put("openmrsVersion", OpenmrsConstants.OPENMRS_VERSION_SHORT);
        map.put("moduleRepositoryURL", WebConstants.MODULE_REPOSITORY_URL);

        map.put("loadedModules", ModuleFactory.getLoadedModules());

        return map;
    }
}