/**
* 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.webservices.rest.web.v1_0.resource.openmrs1_8;
import org.openmrs.module.Module;
import org.openmrs.module.ModuleException;
import org.openmrs.module.webservices.helper.ModuleAction;
import org.openmrs.module.webservices.helper.ModuleFactoryWrapper;
import org.openmrs.module.webservices.rest.SimpleObject;
import org.openmrs.module.webservices.rest.web.ConversionUtil;
import org.openmrs.module.webservices.rest.web.RequestContext;
import org.openmrs.module.webservices.rest.web.RestConstants;
import org.openmrs.module.webservices.rest.web.annotation.PropertyGetter;
import org.openmrs.module.webservices.rest.web.annotation.Resource;
import org.openmrs.module.webservices.rest.web.representation.Representation;
import org.openmrs.module.webservices.rest.web.resource.api.Creatable;
import org.openmrs.module.webservices.rest.web.resource.impl.BaseDelegatingResource;
import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription;
import org.openmrs.module.webservices.rest.web.response.IllegalRequestException;
import org.openmrs.module.webservices.rest.web.response.ResourceDoesNotSupportOperationException;
import org.openmrs.module.webservices.rest.web.response.ResponseException;
import javax.servlet.ServletContext;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Resource(name = RestConstants.VERSION_1 + "/moduleaction", supportedClass = ModuleAction.class, supportedOpenmrsVersions = {
"1.8.*", "1.9.*", "1.10.*", "1.11.*", "1.12.*", "2.0.*", "2.1.*" })
public class ModuleActionResource1_8 extends BaseDelegatingResource<ModuleAction> implements Creatable {
/**
* ModuleFactoryWrapper is used for testing purposes.
*/
private ModuleFactoryWrapper moduleFactoryWrapper = new ModuleFactoryWrapper();
public void setModuleFactoryWrapper(ModuleFactoryWrapper moduleFactoryWrapper) {
this.moduleFactoryWrapper = moduleFactoryWrapper;
}
/**
* Overriding create directly, because ModuleFactory requires ServletContext to execute any
* action
*/
@Override
public Object create(SimpleObject post, RequestContext context) throws ResponseException {
moduleFactoryWrapper.checkPrivilege();
ModuleAction action = newDelegate();
setConvertedProperties(action, post, getCreatableProperties(), true);
Collection<Module> modules;
if (action.isAllModules() != null && action.isAllModules()) {
modules = moduleFactoryWrapper.getLoadedModules();
action.setModules(new ArrayList<Module>(modules));
} else {
modules = action.getModules();
}
ServletContext servletContext = getServletContext(context);
if (modules == null || modules.isEmpty()) {
throw new IllegalRequestException("Cannot execute action " + action.getAction() + " on empty set of modules.");
} else {
if (action.isAllModules() == null || !action.isAllModules()) {
// ensure all specified modules exist
// ensure they're not trying to modify the REST module
for (Module module : modules) {
// if they specified a module that's not loaded, it will show up here as null
if (module == null) {
throw new IllegalRequestException(
"One or more of the modules you specified are not loaded on this server");
}
if (module.getModuleId().equals(RestConstants.MODULE_ID)) {
throw new IllegalRequestException("You are not allowed to modify " + module.getModuleId()
+ " via this REST call");
}
}
}
// even if they said allModule=true, don't touch the REST module
Module restModule = moduleFactoryWrapper.getModuleById(RestConstants.MODULE_ID);
modules.remove(restModule);
switch (action.getAction()) {
case START:
startModules(modules, servletContext);
break;
case STOP:
stopModules(modules, servletContext, true);
break;
case RESTART:
restartModules(modules, servletContext);
break;
case UNLOAD:
unloadModules(modules, servletContext);
break;
}
}
return ConversionUtil.convertToRepresentation(action, Representation.DEFAULT);
}
private void restartModules(Collection<Module> modules, ServletContext servletContext) {
stopModules(modules, servletContext, false);
startModules(modules, servletContext);
}
private void unloadModules(Collection<Module> modules, ServletContext servletContext) {
boolean needsRefresh = false;
for (Module module : modules) {
if (moduleFactoryWrapper.isModuleStarted(module)) {
moduleFactoryWrapper.stopModuleSkipRefresh(module, servletContext);
needsRefresh = true;
}
moduleFactoryWrapper.unloadModule(module);
}
if (needsRefresh) {
moduleFactoryWrapper.refreshWebApplicationContext(servletContext);
}
}
/**
* @param modules modules to stop
* @param servletContext ServletContext is required by WebModuleUtil to perform operation
*/
private void stopModules(Collection<Module> modules, ServletContext servletContext, boolean refreshContext) {
for (Module module : modules) {
if (moduleFactoryWrapper.isModuleStarted(module)) {
moduleFactoryWrapper.stopModuleSkipRefresh(module, servletContext);
}
}
if (refreshContext) {
moduleFactoryWrapper.refreshWebApplicationContext(servletContext);
}
}
/**
* @param modules modules to start
* @param servletContext ServletContext is required by WebModuleUtil to perform operation
*/
private void startModules(Collection<Module> modules, ServletContext servletContext) {
boolean needsRefresh = false;
if (modules.size() > 1) {
modules = moduleFactoryWrapper.getModulesInStartupOrder(modules);
}
for (Module module : modules) {
if (moduleFactoryWrapper.isModuleStopped(module)) {
needsRefresh = moduleFactoryWrapper.startModuleSkipRefresh(module, servletContext) || needsRefresh;
}
}
//check if any module has been started, doesn't refresh WAC if all modules failed to start
if (needsRefresh) {
moduleFactoryWrapper.refreshWebApplicationContext(servletContext);
}
findAndThrowStartupErrors(modules);
}
private void findAndThrowStartupErrors(Collection<Module> modules) {
List<Exception> errors = new ArrayList<Exception>();
for (Module module : modules) {
if (moduleFactoryWrapper.isModuleStopped(module)) {
//module actions are executed in other thread, so we need to explicitly check and throw them
if (module.getStartupErrorMessage() != null) {
errors.add(new ModuleException(module.getStartupErrorMessage()));
}
}
}
if (!errors.isEmpty()) {
StringBuilder stringBuilder = new StringBuilder();
for (Exception error : errors) {
stringBuilder.append(error.getMessage()).append("; ");
}
throw new RuntimeException(stringBuilder.toString());
}
}
@Override
public ModuleAction newDelegate() {
return new ModuleAction();
}
@Override
public ModuleAction save(ModuleAction delegate) {
throw new UnsupportedOperationException("ModuleAction cannot be saved");
}
@Override
public ModuleAction getByUniqueId(String uniqueId) {
throw new ResourceDoesNotSupportOperationException();
}
@Override
protected void delete(ModuleAction delegate, String reason, RequestContext context) throws ResponseException {
throw new ResourceDoesNotSupportOperationException();
}
@Override
public void purge(ModuleAction delegate, RequestContext context) throws ResponseException {
throw new ResourceDoesNotSupportOperationException();
}
@Override
public DelegatingResourceDescription getRepresentationDescription(Representation rep) {
DelegatingResourceDescription description = new DelegatingResourceDescription();
description.addProperty("modules", Representation.REF);
description.addProperty("action", "action");
return description;
}
@Override
public DelegatingResourceDescription getCreatableProperties() throws ResourceDoesNotSupportOperationException {
DelegatingResourceDescription description = new DelegatingResourceDescription();
description.addProperty("modules");
description.addProperty("allModules");
description.addRequiredProperty("action", "action");
return description;
}
/**
* Converter does not handle getters starting with 'is' instead of 'get'
*/
@PropertyGetter("allModules")
public Boolean isAllModules(ModuleAction action) {
return action.isAllModules();
}
private ServletContext getServletContext(RequestContext context) {
return context.getRequest().getSession().getServletContext();
}
}