/api/src/main/java/org/openmrs/api/context/Daemon.java

https://github.com/babitha/openmrs · Java · 254 lines · 136 code · 36 blank · 82 comment · 12 complexity · c7e22d37cde63de43b9b2814b7485db2 MD5 · raw file

  1. /**
  2. * The contents of this file are subject to the OpenMRS Public License
  3. * Version 1.0 (the "License"); you may not use this file except in
  4. * compliance with the License. You may obtain a copy of the License at
  5. * http://license.openmrs.org
  6. *
  7. * Software distributed under the License is distributed on an "AS IS"
  8. * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
  9. * License for the specific language governing rights and limitations
  10. * under the License.
  11. *
  12. * Copyright (C) OpenMRS, LLC. All Rights Reserved.
  13. */
  14. package org.openmrs.api.context;
  15. import org.openmrs.api.APIAuthenticationException;
  16. import org.openmrs.api.APIException;
  17. import org.openmrs.api.OpenmrsService;
  18. import org.openmrs.module.Module;
  19. import org.openmrs.module.ModuleException;
  20. import org.openmrs.module.ModuleFactory;
  21. import org.openmrs.scheduler.Task;
  22. import org.openmrs.scheduler.timer.TimerSchedulerTask;
  23. import org.openmrs.util.OpenmrsSecurityManager;
  24. /**
  25. * This class allows certain tasks to run with elevated privileges. Primary use is scheduling and
  26. * module startup when there is no user to authenticate as.
  27. */
  28. public class Daemon {
  29. /**
  30. * The uuid defined for the daemon user object
  31. */
  32. protected static final String DAEMON_USER_UUID = "A4F30A1B-5EB9-11DF-A648-37A07F9C90FB";
  33. private static final ThreadLocal<Boolean> isDaemonThread = new ThreadLocal<Boolean>();
  34. /**
  35. * This method should not be called directly. The {@link ModuleFactory#startModule(Module)}
  36. * method uses this to start the given module in a new thread that is authenticated as the
  37. * daemon user
  38. *
  39. * @param module the module to start
  40. * @returns the module returned from {@link ModuleFactory#startModuleInternal(Module)}
  41. */
  42. public static Module startModule(final Module module) throws ModuleException {
  43. // create a new thread and execute that task in it
  44. DaemonThread startModuleThread = new DaemonThread() {
  45. @Override
  46. public void run() {
  47. isDaemonThread.set(true);
  48. try {
  49. Context.openSession();
  50. returnedObject = ModuleFactory.startModuleInternal(module);
  51. }
  52. catch (Throwable t) {
  53. exceptionThrown = t;
  54. }
  55. finally {
  56. Context.closeSession();
  57. }
  58. }
  59. };
  60. startModuleThread.start();
  61. // wait for the "startModule" thread to finish
  62. try {
  63. startModuleThread.join();
  64. }
  65. catch (InterruptedException e) {
  66. // ignore
  67. }
  68. if (startModuleThread.exceptionThrown != null) {
  69. if (startModuleThread.exceptionThrown instanceof ModuleException)
  70. throw (ModuleException) startModuleThread.exceptionThrown;
  71. else
  72. throw new ModuleException("Unable to start module as Daemon", startModuleThread.exceptionThrown);
  73. }
  74. return (Module) startModuleThread.returnedObject;
  75. }
  76. /**
  77. * Executes the given task in a new thread that is authenticated as the daemon user. <br/>
  78. * <br/>
  79. * This can only be called from {@link TimerSchedulerTask} during actual task execution
  80. *
  81. * @param task the task to run
  82. * @should not be called from other methods other than TimerSchedulerTask
  83. * @should not throw error if called from a TimerSchedulerTask class
  84. */
  85. public static void executeScheduledTask(final Task task) throws Throwable {
  86. // quick check to make sure we're only being called by ourselves
  87. //Class<?> callerClass = Reflection.getCallerClass(0);
  88. Class<?> callerClass = new OpenmrsSecurityManager().getCallerClass(0);
  89. if (!TimerSchedulerTask.class.isAssignableFrom(callerClass))
  90. throw new APIException("This method can only be called from the TimerSchedulerTask class, not "
  91. + callerClass.getName());
  92. // now create a new thread and execute that task in it
  93. DaemonThread executeTaskThread = new DaemonThread() {
  94. @Override
  95. public void run() {
  96. isDaemonThread.set(true);
  97. try {
  98. Context.openSession();
  99. task.execute();
  100. }
  101. catch (Throwable t) {
  102. exceptionThrown = t;
  103. }
  104. finally {
  105. Context.closeSession();
  106. }
  107. }
  108. };
  109. executeTaskThread.start();
  110. // wait for the "executeTaskThread" thread to finish
  111. try {
  112. executeTaskThread.join();
  113. }
  114. catch (InterruptedException e) {
  115. // ignore
  116. }
  117. if (executeTaskThread.exceptionThrown != null)
  118. throw executeTaskThread.exceptionThrown;
  119. }
  120. /**
  121. * Call this method if you are inside a Daemon thread (for example in a Module activator or a
  122. * scheduled task) and you want to start up a new parallel Daemon thread. You may only call this
  123. * method from a Daemon thread.
  124. *
  125. * @param runnable what to run in a new thread
  126. * @return the newly spawned {@link Thread}
  127. * @should throw error if called from a non daemon thread
  128. * @should not throw error if called from a daemon thread
  129. */
  130. public static Thread runInNewDaemonThread(final Runnable runnable) {
  131. // make sure we're already in a daemon thread
  132. if (!isDaemonThread())
  133. throw new APIAuthenticationException("Only daemon threads can spawn new daemon threads");
  134. // we should consider making DaemonThread public, so the caller can access returnedObject and exceptionThrown
  135. DaemonThread thread = new DaemonThread() {
  136. @Override
  137. public void run() {
  138. isDaemonThread.set(true);
  139. try {
  140. Context.openSession();
  141. runnable.run();
  142. }
  143. finally {
  144. Context.closeSession();
  145. }
  146. }
  147. };
  148. thread.start();
  149. return thread;
  150. }
  151. /**
  152. * @return true if the current thread was started by this class and so is a daemon thread that
  153. * has all privileges
  154. * @see Context#hasPrivilege(String)
  155. */
  156. public static boolean isDaemonThread() {
  157. Boolean b = isDaemonThread.get();
  158. if (b == null)
  159. return false;
  160. else
  161. return b.booleanValue();
  162. }
  163. /**
  164. * Calls the {@link OpenmrsService#onStartup()} method, as a daemon, for an instance
  165. * implementing the {@link OpenmrsService} interface.
  166. *
  167. * @param openmrsService instance implementing the {@link OpenmrsService} interface.
  168. * @since 1.9
  169. */
  170. public static void runStartupForService(final OpenmrsService service) throws ModuleException {
  171. DaemonThread onStartupThread = new DaemonThread() {
  172. @Override
  173. public void run() {
  174. isDaemonThread.set(true);
  175. try {
  176. Context.openSession();
  177. service.onStartup();
  178. }
  179. catch (Throwable t) {
  180. exceptionThrown = t;
  181. }
  182. finally {
  183. Context.closeSession();
  184. }
  185. }
  186. };
  187. onStartupThread.start();
  188. // wait for the "onStartup" thread to finish
  189. try {
  190. onStartupThread.join();
  191. }
  192. catch (InterruptedException e) {
  193. // ignore
  194. e.printStackTrace();
  195. }
  196. if (onStartupThread.exceptionThrown != null) {
  197. if (onStartupThread.exceptionThrown instanceof ModuleException)
  198. throw (ModuleException) onStartupThread.exceptionThrown;
  199. else
  200. throw new ModuleException("Unable to run onStartup() method as Daemon", onStartupThread.exceptionThrown);
  201. }
  202. }
  203. /**
  204. * Thread class used by the {@link Daemon#startModule(Module)} and
  205. * {@link Daemon#executeScheduledTask(Task)} methods so that the returned object and the
  206. * exception thrown can be returned to calling class
  207. */
  208. private static class DaemonThread extends Thread {
  209. /**
  210. * The object returned from the method called in {@link #run()}
  211. */
  212. protected Object returnedObject = null;
  213. /**
  214. * The exception thrown (if any) by the method called in {@link #run()}
  215. */
  216. protected Throwable exceptionThrown = null;
  217. }
  218. }