Advertisement
Guest User

Untitled

a guest
Mar 13th, 2014
94
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 37.02 KB | None | 0 0
  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.module;
  15.  
  16. import java.io.ByteArrayOutputStream;
  17. import java.io.File;
  18. import java.io.FileNotFoundException;
  19. import java.io.FileOutputStream;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.OutputStream;
  23. import java.net.HttpURLConnection;
  24. import java.net.MalformedURLException;
  25. import java.net.URL;
  26. import java.net.URLConnection;
  27. import java.util.ArrayList;
  28. import java.util.Collection;
  29. import java.util.Collections;
  30. import java.util.Enumeration;
  31. import java.util.HashMap;
  32. import java.util.HashSet;
  33. import java.util.LinkedHashSet;
  34. import java.util.List;
  35. import java.util.Map;
  36. import java.util.Properties;
  37. import java.util.Set;
  38. import java.util.Vector;
  39. import java.util.jar.JarEntry;
  40. import java.util.jar.JarFile;
  41.  
  42. import org.apache.commons.lang.StringUtils;
  43. import org.apache.commons.lang.math.NumberUtils;
  44. import org.apache.commons.logging.Log;
  45. import org.apache.commons.logging.LogFactory;
  46. import org.openmrs.GlobalProperty;
  47. import org.openmrs.api.AdministrationService;
  48. import org.openmrs.api.context.Context;
  49. import org.openmrs.api.context.ServiceContext;
  50. import org.openmrs.util.OpenmrsClassLoader;
  51. import org.openmrs.util.OpenmrsUtil;
  52. import org.springframework.context.support.AbstractRefreshableApplicationContext;
  53.  
  54. /**
  55. * Utility methods for working and manipulating modules
  56. */
  57. public class ModuleUtil {
  58.  
  59. private static Log log = LogFactory.getLog(ModuleUtil.class);
  60.  
  61. /**
  62. * Start up the module system with the given properties.
  63. *
  64. * @param props Properties (OpenMRS runtime properties)
  65. */
  66. public static void startup(Properties props) throws ModuleMustStartException, OpenmrsCoreModuleException {
  67.  
  68. String moduleListString = props.getProperty(ModuleConstants.RUNTIMEPROPERTY_MODULE_LIST_TO_LOAD);
  69.  
  70. if (moduleListString == null || moduleListString.length() == 0) {
  71. // Attempt to get all of the modules from the modules folder
  72. // and store them in the modules list
  73. log.debug("Starting all modules");
  74. ModuleFactory.loadModules();
  75. } else {
  76. // use the list of modules and load only those
  77. log.debug("Starting all modules in this list: " + moduleListString);
  78.  
  79. String[] moduleArray = moduleListString.split(" ");
  80. List<File> modulesToLoad = new Vector<File>();
  81.  
  82. for (String modulePath : moduleArray) {
  83. if (modulePath != null && modulePath.length() > 0) {
  84. File file = new File(modulePath);
  85. if (file.exists())
  86. modulesToLoad.add(file);
  87. else {
  88. // try to load the file from the classpath
  89. InputStream stream = ModuleUtil.class.getClassLoader().getResourceAsStream(modulePath);
  90.  
  91. // expand the classpath-found file to a temporary location
  92. if (stream != null) {
  93. try {
  94. // get and make a temp directory if necessary
  95. String tmpDir = System.getProperty("java.io.tmpdir");
  96. File expandedFile = File.createTempFile(file.getName() + "-", ".omod", new File(tmpDir));
  97.  
  98. // pull the name from the absolute path load attempt
  99. FileOutputStream outStream = new FileOutputStream(expandedFile, false);
  100.  
  101. // do the actual file copying
  102. OpenmrsUtil.copyFile(stream, outStream);
  103.  
  104. // add the freshly expanded file to the list of modules we're going to start up
  105. modulesToLoad.add(expandedFile);
  106. expandedFile.deleteOnExit();
  107. }
  108. catch (IOException io) {
  109. log.error("Unable to expand classpath found module: " + modulePath, io);
  110. }
  111. } else
  112. log
  113. .error("Unable to load module at path: "
  114. + modulePath
  115. + " because no file exists there and it is not found on the classpath. (absolute path tried: "
  116. + file.getAbsolutePath() + ")");
  117. }
  118. }
  119. }
  120.  
  121. ModuleFactory.loadModules(modulesToLoad);
  122. }
  123.  
  124. // start all of the modules we just loaded
  125. ModuleFactory.startModules();
  126.  
  127. // some debugging info
  128. if (log.isDebugEnabled()) {
  129. Collection<Module> modules = ModuleFactory.getStartedModules();
  130. if (modules == null || modules.size() == 0)
  131. log.debug("No modules loaded");
  132. else
  133. log.debug("Found and loaded " + modules.size() + " module(s)");
  134. }
  135.  
  136. // make sure all openmrs required moduls are loaded and started
  137. checkOpenmrsCoreModulesStarted();
  138.  
  139. // make sure all mandatory modules are loaded and started
  140. checkMandatoryModulesStarted();
  141. }
  142.  
  143. /**
  144. * Stops the module system by calling stopModule for all modules that are currently started
  145. */
  146. public static void shutdown() {
  147.  
  148. List<Module> modules = new Vector<Module>();
  149. modules.addAll(ModuleFactory.getStartedModules());
  150.  
  151. for (Module mod : modules) {
  152. if (log.isDebugEnabled())
  153. log.debug("stopping module: " + mod.getModuleId());
  154.  
  155. if (mod.isStarted())
  156. ModuleFactory.stopModule(mod, true, true);
  157. }
  158.  
  159. log.debug("done shutting down modules");
  160.  
  161. // clean up the static variables just in case they weren't done before
  162. ModuleFactory.extensionMap = null;
  163. ModuleFactory.loadedModules = null;
  164. ModuleFactory.moduleClassLoaders = null;
  165. ModuleFactory.startedModules = null;
  166. }
  167.  
  168. /**
  169. * Add the <code>inputStream</code> as a file in the modules repository
  170. *
  171. * @param inputStream <code>InputStream</code> to load
  172. * @return filename String of the file's name of the stream
  173. */
  174. public static File insertModuleFile(InputStream inputStream, String filename) {
  175. File folder = getModuleRepository();
  176.  
  177. // check if module filename is already loaded
  178. if (OpenmrsUtil.folderContains(folder, filename))
  179. throw new ModuleException(filename + " is already associated with a loaded module.");
  180.  
  181. File file = new File(folder.getAbsolutePath(), filename);
  182.  
  183. FileOutputStream outputStream = null;
  184. try {
  185. outputStream = new FileOutputStream(file);
  186. OpenmrsUtil.copyFile(inputStream, outputStream);
  187. }
  188. catch (FileNotFoundException e) {
  189. throw new ModuleException("Can't create module file for " + filename, e);
  190. }
  191. catch (IOException e) {
  192. throw new ModuleException("Can't create module file for " + filename, e);
  193. }
  194. finally {
  195. try {
  196. inputStream.close();
  197. }
  198. catch (Exception e) { /* pass */}
  199. try {
  200. outputStream.close();
  201. }
  202. catch (Exception e) { /* pass */}
  203. }
  204.  
  205. return file;
  206. }
  207.  
  208. /**
  209. * This method is an enhancement of {@link #compareVersion(String, String)} and adds support for
  210. * wildcard characters and upperbounds. <br/>
  211. * <br/>
  212. * This method calls {@link ModuleUtil#checkRequiredVersion(String, String)} internally. <br/>
  213. * <br/>
  214. * The require version number in the config file can be in the following format:
  215. * <ul>
  216. * <li>1.2.3</li>
  217. * <li>1.2.*</li>
  218. * <li>1.2.2 - 1.2.3</li>
  219. * <li>1.2.* - 1.3.*</li>
  220. * </ul>
  221. *
  222. * @param version openmrs version number to be compared
  223. * @param versionRange value in the config file for required openmrs version
  224. * @return true if the <code>version</code> is within the <code>value</code>
  225. * @should allow ranged required version
  226. * @should allow ranged required version with wild card
  227. * @should allow ranged required version with wild card on one end
  228. * @should allow single entry for required version
  229. * @should allow required version with wild card
  230. * @should allow non numeric character required version
  231. * @should allow ranged non numeric character required version
  232. * @should allow ranged non numeric character with wild card
  233. * @should allow ranged non numeric character with wild card on one end
  234. * @should return false when openmrs version beyond wild card range
  235. * @should return false when required version beyond openmrs version
  236. * @should return false when required version with wild card beyond openmrs version
  237. * @should return false when required version with wild card on one end beyond openmrs version
  238. * @should return false when single entry required version beyond openmrs version
  239. * @should allow release type in the version
  240. */
  241. public static boolean matchRequiredVersions(String version, String versionRange) {
  242. if (versionRange != null && !versionRange.equals("")) {
  243. String[] ranges = versionRange.split(",");
  244. for (String range : ranges) {
  245. // need to externalize this string
  246. String separator = "-";
  247. if (range.indexOf("*") > 0 || range.indexOf(separator) > 0) {
  248. // if it contains "*" or "-" then we must separate those two
  249. // assume it's always going to be two part
  250. // assign the upper and lower bound
  251. // if there's no "-" to split lower and upper bound
  252. // then assign the same value for the lower and upper
  253. String lowerBound = range;
  254. String upperBound = range;
  255.  
  256. int indexOfSeparator = range.indexOf(separator);
  257. while (indexOfSeparator > 0) {
  258. lowerBound = range.substring(0, indexOfSeparator);
  259. upperBound = range.substring(indexOfSeparator + 1);
  260. if (upperBound.matches("^\\s?\\d+.*"))
  261. break;
  262. indexOfSeparator = range.indexOf(separator, indexOfSeparator + 1);
  263. }
  264.  
  265. // only preserve part of the string that match the following format:
  266. // - xx.yy.*
  267. // - xx.yy.zz*
  268. lowerBound = StringUtils.remove(lowerBound, lowerBound.replaceAll("^\\s?\\d+[\\.\\d+\\*?|\\.\\*]+", ""));
  269. upperBound = StringUtils.remove(upperBound, upperBound.replaceAll("^\\s?\\d+[\\.\\d+\\*?|\\.\\*]+", ""));
  270.  
  271. // if the lower contains "*" then change it to zero
  272. if (lowerBound.indexOf("*") > 0)
  273. lowerBound = lowerBound.replaceAll("\\*", "0");
  274.  
  275. // if the upper contains "*" then change it to 999
  276. // assuming 999 will be the max revision number for openmrs
  277. if (upperBound.indexOf("*") > 0)
  278. upperBound = upperBound.replaceAll("\\*", "999");
  279.  
  280. int lowerReturn = compareVersion(version, lowerBound);
  281.  
  282. int upperReturn = compareVersion(version, upperBound);
  283.  
  284. if (lowerReturn < 0 || upperReturn > 0) {
  285. log.debug("Version " + version + " is not between " + lowerBound + " and " + upperBound);
  286. } else {
  287. return true;
  288. }
  289. } else {
  290. if (compareVersion(version, range) < 0) {
  291. log.debug("Version " + version + " is below " + range);
  292. } else {
  293. return true;
  294. }
  295. }
  296. }
  297. }
  298. return false;
  299. }
  300.  
  301. /**
  302. * This method is an enhancement of {@link #compareVersion(String, String)} and adds support for
  303. * wildcard characters and upperbounds. <br/>
  304. * <br/>
  305. * <br/>
  306. * The require version number in the config file can be in the following format:
  307. * <ul>
  308. * <li>1.2.3</li>
  309. * <li>1.2.*</li>
  310. * <li>1.2.2 - 1.2.3</li>
  311. * <li>1.2.* - 1.3.*</li>
  312. * </ul>
  313. * <br/>
  314. *
  315. * @param version openmrs version number to be compared
  316. * @param versionRange value in the config file for required openmrs version
  317. * @throws ModuleException if the <code>version</code> is not within the <code>value</code>
  318. * @should throw ModuleException if openmrs version beyond wild card range
  319. * @should throw ModuleException if required version beyond openmrs version
  320. * @should throw ModuleException if required version with wild card beyond openmrs version
  321. * @should throw ModuleException if required version with wild card on one end beyond openmrs
  322. * version
  323. * @should throw ModuleException if single entry required version beyond openmrs version
  324. */
  325. public static void checkRequiredVersion(String version, String versionRange) throws ModuleException {
  326. if (!matchRequiredVersions(version, versionRange)) {
  327. String ms = Context.getMessageSourceService().getMessage("Module.requireVersion.outOfBounds",
  328. new String[] { versionRange, version }, Context.getLocale());
  329. throw new ModuleException(ms);
  330. }
  331. }
  332.  
  333. /**
  334. * Compares <code>version</code> to <code>value</code> version and value are strings like
  335. * w.x.y.z Returns <code>0</code> if either <code>version</code> or <code>value</code> is null.
  336. *
  337. * @param version String like w.x.y.z
  338. * @param value String like w.x.y.z
  339. * @return the value <code>0</code> if <code>version</code> is equal to the argument
  340. * <code>value</code>; a value less than <code>0</code> if <code>version</code> is
  341. * numerically less than the argument <code>value</code>; and a value greater than
  342. * <code>0</code> if <code>version</code> is numerically greater than the argument
  343. * <code>value</code>
  344. * @should correctly comparing two version numbers
  345. * @should treat SNAPSHOT as earliest version
  346. */
  347. public static int compareVersion(String version, String value) {
  348. try {
  349. if (version == null || value == null)
  350. return 0;
  351.  
  352. List<String> versions = new Vector<String>();
  353. List<String> values = new Vector<String>();
  354.  
  355. // treat "-SNAPSHOT" as the lowest possible version
  356. // e.g. 1.8.4-SNAPSHOT is really 1.8.4.0
  357. version = version.replace("-SNAPSHOT", ".0");
  358. value = value.replace("-SNAPSHOT", ".0");
  359.  
  360. Collections.addAll(versions, version.split("\\."));
  361. Collections.addAll(values, value.split("\\."));
  362.  
  363. // match the sizes of the lists
  364. while (versions.size() < values.size()) {
  365. versions.add("0");
  366. }
  367. while (values.size() < versions.size()) {
  368. values.add("0");
  369. }
  370.  
  371. for (int x = 0; x < versions.size(); x++) {
  372. String verNum = versions.get(x).trim();
  373. String valNum = values.get(x).trim();
  374. Integer ver = NumberUtils.toInt(verNum, 0);
  375. Integer val = NumberUtils.toInt(valNum, 0);
  376.  
  377. int ret = ver.compareTo(val);
  378. if (ret != 0)
  379. return ret;
  380. }
  381. }
  382. catch (NumberFormatException e) {
  383. log.error("Error while converting a version/value to an integer: " + version + "/" + value, e);
  384. }
  385.  
  386. // default return value if an error occurs or elements are equal
  387. return 0;
  388. }
  389.  
  390. /**
  391. * Gets the folder where modules are stored. ModuleExceptions are thrown on errors
  392. *
  393. * @return folder containing modules
  394. */
  395. public static File getModuleRepository() {
  396.  
  397. AdministrationService as = Context.getAdministrationService();
  398. String folderName = as.getGlobalProperty(ModuleConstants.REPOSITORY_FOLDER_PROPERTY,
  399. ModuleConstants.REPOSITORY_FOLDER_PROPERTY_DEFAULT);
  400.  
  401. // try to load the repository folder straight away.
  402. File folder = new File(folderName);
  403.  
  404. // if the property wasn't a full path already, assume it was intended to be a folder in the
  405. // application directory
  406. if (!folder.exists()) {
  407. folder = new File(OpenmrsUtil.getApplicationDataDirectory(), folderName);
  408. }
  409.  
  410. // now create the modules folder if it doesn't exist
  411. if (!folder.exists()) {
  412. log.warn("Module repository " + folder.getAbsolutePath() + " doesn't exist. Creating directories now.");
  413. folder.mkdirs();
  414. }
  415.  
  416. if (!folder.isDirectory())
  417. throw new ModuleException("Module repository is not a directory at: " + folder.getAbsolutePath());
  418.  
  419. return folder;
  420. }
  421.  
  422. /**
  423. * Utility method to convert a {@link File} object to a local URL.
  424. *
  425. * @param file a file object
  426. * @return absolute URL that points to the given file
  427. * @throws MalformedURLException if file can't be represented as URL for some reason
  428. */
  429. public static URL file2url(final File file) throws MalformedURLException {
  430. if (file == null)
  431. return null;
  432. try {
  433. return file.getCanonicalFile().toURI().toURL();
  434. }
  435. catch (MalformedURLException mue) {
  436. throw mue;
  437. }
  438. catch (IOException ioe) {
  439. throw new MalformedURLException("Cannot convert: " + file.getName() + " to url");
  440. }
  441. catch (NoSuchMethodError nsme) {
  442. throw new MalformedURLException("Cannot convert: " + file.getName() + " to url");
  443. }
  444. }
  445.  
  446. /**
  447. * Expand the given <code>fileToExpand</code> jar to the <code>tmpModuleFile<code> directory
  448. *
  449. * If <code>name</code> is null, the entire jar is expanded. If<code>name</code> is not null,
  450. * then only that path/file is expanded.
  451. *
  452. * @param fileToExpand file pointing at a .jar
  453. * @param tmpModuleDir directory in which to place the files
  454. * @param name filename inside of the jar to look for and expand
  455. * @param keepFullPath if true, will recreate entire directory structure in tmpModuleDir
  456. * relating to <code>name</code>. if false will start directory structure at
  457. * <code>name</code>
  458. */
  459. @SuppressWarnings("unchecked")
  460. public static void expandJar(File fileToExpand, File tmpModuleDir, String name, boolean keepFullPath) throws IOException {
  461. JarFile jarFile = null;
  462. InputStream input = null;
  463. String docBase = tmpModuleDir.getAbsolutePath();
  464. try {
  465. jarFile = new JarFile(fileToExpand);
  466. Enumeration jarEntries = jarFile.entries();
  467. boolean foundName = (name == null);
  468.  
  469. // loop over all of the elements looking for the match to 'name'
  470. while (jarEntries.hasMoreElements()) {
  471. JarEntry jarEntry = (JarEntry) jarEntries.nextElement();
  472. if (name == null || jarEntry.getName().startsWith(name)) {
  473. String entryName = jarEntry.getName();
  474. // trim out the name path from the name of the new file
  475. if (keepFullPath == false && name != null)
  476. entryName = entryName.replaceFirst(name, "");
  477.  
  478. // if it has a slash, it's in a directory
  479. int last = entryName.lastIndexOf('/');
  480. if (last >= 0) {
  481. File parent = new File(docBase, entryName.substring(0, last));
  482. parent.mkdirs();
  483. log.debug("Creating parent dirs: " + parent.getAbsolutePath());
  484. }
  485. // we don't want to "expand" directories or empty names
  486. if (entryName.endsWith("/") || entryName.equals("")) {
  487. continue;
  488. }
  489. input = jarFile.getInputStream(jarEntry);
  490. expand(input, docBase, entryName);
  491. input.close();
  492. input = null;
  493. foundName = true;
  494. }
  495. }
  496. if (!foundName)
  497. log.debug("Unable to find: " + name + " in file " + fileToExpand.getAbsolutePath());
  498.  
  499. }
  500. catch (IOException e) {
  501. log.warn("Unable to delete tmpModuleFile on error", e);
  502. throw e;
  503. }
  504. finally {
  505. try {
  506. input.close();
  507. }
  508. catch (Exception e) { /* pass */}
  509. try {
  510. jarFile.close();
  511. }
  512. catch (Exception e) { /* pass */}
  513. }
  514. }
  515.  
  516. /**
  517. * Expand the given file in the given stream to a location (fileDir/name) The <code>input</code>
  518. * InputStream is not closed in this method
  519. *
  520. * @param input stream to read from
  521. * @param fileDir directory to copy to
  522. * @param name file/directory within the <code>fileDir</code> to which we expand
  523. * <code>input</code>
  524. * @return File the file created by the expansion.
  525. * @throws IOException if an error occurred while copying
  526. */
  527. private static File expand(InputStream input, String fileDir, String name) throws IOException {
  528. if (log.isDebugEnabled())
  529. log.debug("expanding: " + name);
  530.  
  531. File file = new File(fileDir, name);
  532. FileOutputStream outStream = null;
  533. try {
  534. outStream = new FileOutputStream(file);
  535. OpenmrsUtil.copyFile(input, outStream);
  536. }
  537. finally {
  538. try {
  539. outStream.close();
  540. }
  541. catch (Exception e) { /* pass */}
  542. }
  543.  
  544. return file;
  545. }
  546.  
  547. /**
  548. * Downloads the contents of a URL and copies them to a string (Borrowed from oreilly)
  549. *
  550. * @param url
  551. * @return InputStream of contents
  552. * @should return a valid input stream for old module urls
  553. */
  554. public static InputStream getURLStream(URL url) {
  555. InputStream in = null;
  556. try {
  557. URLConnection uc = url.openConnection();
  558. uc.setDefaultUseCaches(false);
  559. uc.setUseCaches(false);
  560. uc.setRequestProperty("Cache-Control", "max-age=0,no-cache");
  561. uc.setRequestProperty("Pragma", "no-cache");
  562.  
  563. log.error("Logging an attempt to connect to: " + url);
  564.  
  565. in = openConnectionCheckRedirects(uc);
  566. }
  567. catch (IOException io) {
  568. log.warn("io while reading: " + url, io);
  569. }
  570.  
  571. return in;
  572. }
  573.  
  574. /**
  575. * Convenience method to follow http to https redirects. Will follow a total of 5 redirects,
  576. * then fail out due to foolishness on the url's part.
  577. *
  578. * @param c the {@link URLConnection} to open
  579. * @return an {@link InputStream} that is not necessarily at the same url, possibly at a 403 redirect.
  580. * @throws IOException
  581. * @see {@link #getURLStream(URL)}
  582. */
  583. protected static InputStream openConnectionCheckRedirects(URLConnection c) throws IOException {
  584. boolean redir;
  585. int redirects = 0;
  586. InputStream in = null;
  587. do {
  588. if (c instanceof HttpURLConnection) {
  589. ((HttpURLConnection) c).setInstanceFollowRedirects(false);
  590. }
  591. // We want to open the input stream before getting headers
  592. // because getHeaderField() et al swallow IOExceptions.
  593. in = c.getInputStream();
  594. redir = false;
  595. if (c instanceof HttpURLConnection) {
  596. HttpURLConnection http = (HttpURLConnection) c;
  597. int stat = http.getResponseCode();
  598. if (stat == 300 || stat == 301 || stat == 302 || stat == 303 || stat == 305 || stat == 307) {
  599. URL base = http.getURL();
  600. String loc = http.getHeaderField("Location");
  601. URL target = null;
  602. if (loc != null) {
  603. target = new URL(base, loc);
  604. }
  605. http.disconnect();
  606. // Redirection should be allowed only for HTTP and HTTPS
  607. // and should be limited to 5 redirections at most.
  608. if (target == null || !(target.getProtocol().equals("http") || target.getProtocol().equals("https"))
  609. || redirects >= 5) {
  610. throw new SecurityException("illegal URL redirect");
  611. }
  612. redir = true;
  613. c = target.openConnection();
  614. redirects++;
  615. }
  616. }
  617. } while (redir);
  618. return in;
  619. }
  620.  
  621. /**
  622. * Downloads the contents of a URL and copies them to a string (Borrowed from oreilly)
  623. *
  624. * @param url
  625. * @return String contents of the URL
  626. * @should return an update rdf page for old https dev urls
  627. * @should return an update rdf page for old https module urls
  628. * @should return an update rdf page for module urls
  629. */
  630. public static String getURL(URL url) {
  631. InputStream in = null;
  632. OutputStream out = null;
  633. String output = "";
  634. try {
  635. in = getURLStream(url);
  636. if (in == null) // skip this module if updateURL is not defined
  637. return "";
  638.  
  639. out = new ByteArrayOutputStream();
  640. OpenmrsUtil.copyFile(in, out);
  641. output = out.toString();
  642. }
  643. catch (IOException io) {
  644. log.warn("io while reading: " + url, io);
  645. }
  646. finally {
  647. try {
  648. in.close();
  649. }
  650. catch (Exception e) { /* pass */}
  651. try {
  652. out.close();
  653. }
  654. catch (Exception e) { /* pass */}
  655. }
  656.  
  657. return output;
  658. }
  659.  
  660. /**
  661. * Iterates over the modules and checks each update.rdf file for an update
  662. *
  663. * @return True if an update was found for one of the modules, false if none were found
  664. * @throws ModuleException
  665. */
  666. public static Boolean checkForModuleUpdates() throws ModuleException {
  667.  
  668. Boolean updateFound = false;
  669.  
  670. for (Module mod : ModuleFactory.getLoadedModules()) {
  671. String updateURL = mod.getUpdateURL();
  672. if (updateURL != null && !updateURL.equals("")) {
  673. try {
  674. // get the contents pointed to by the url
  675. URL url = new URL(updateURL);
  676. if (!url.toString().endsWith(ModuleConstants.UPDATE_FILE_NAME)) {
  677. log.warn("Illegal url: " + url);
  678. continue;
  679. }
  680. String content = getURL(url);
  681.  
  682. // skip empty or invalid updates
  683. if (content.equals(""))
  684. continue;
  685.  
  686. // process and parse the contents
  687. UpdateFileParser parser = new UpdateFileParser(content);
  688. parser.parse();
  689.  
  690. log.debug("Update for mod: " + mod.getModuleId() + " compareVersion result: "
  691. + compareVersion(mod.getVersion(), parser.getCurrentVersion()));
  692.  
  693. // check the udpate.rdf version against the installed version
  694. if (compareVersion(mod.getVersion(), parser.getCurrentVersion()) < 0) {
  695. if (mod.getModuleId().equals(parser.getModuleId())) {
  696. mod.setDownloadURL(parser.getDownloadURL());
  697. mod.setUpdateVersion(parser.getCurrentVersion());
  698. updateFound = true;
  699. } else
  700. log.warn("Module id does not match in update.rdf:" + parser.getModuleId());
  701. } else {
  702. mod.setDownloadURL(null);
  703. mod.setUpdateVersion(null);
  704. }
  705. }
  706. catch (ModuleException e) {
  707. log.warn("Unable to get updates from update.xml", e);
  708. }
  709. catch (MalformedURLException e) {
  710. log.warn("Unable to form a URL object out of: " + updateURL, e);
  711. }
  712. }
  713. }
  714.  
  715. return updateFound;
  716. }
  717.  
  718. /**
  719. * @return true/false whether the 'allow upload' or 'allow web admin' property has been turned
  720. * on
  721. */
  722. public static Boolean allowAdmin() {
  723.  
  724. Properties properties = Context.getRuntimeProperties();
  725. String prop = properties.getProperty(ModuleConstants.RUNTIMEPROPERTY_ALLOW_UPLOAD, null);
  726. if (prop == null)
  727. prop = properties.getProperty(ModuleConstants.RUNTIMEPROPERTY_ALLOW_ADMIN, "false");
  728.  
  729. return "true".equals(prop);
  730. }
  731.  
  732. /**
  733. * @see ModuleUtil#refreshApplicationContext(AbstractRefreshableApplicationContext, Module)
  734. */
  735. public static AbstractRefreshableApplicationContext refreshApplicationContext(AbstractRefreshableApplicationContext ctx) {
  736. return refreshApplicationContext(ctx, false, null);
  737. }
  738.  
  739. /**
  740. * Refreshes the given application context "properly" in OpenMRS. Will first shut down the
  741. * Context and destroy the classloader, then will refresh and set everything back up again.
  742. *
  743. * @param ctx Spring application context that needs refreshing.
  744. * @param isOpenmrsStartup if this refresh is being done at application startup.
  745. * @param startedModule the module that was just started and waiting on the context refresh.
  746. * @return AbstractRefreshableApplicationContext The newly refreshed application context.
  747. */
  748. public static AbstractRefreshableApplicationContext refreshApplicationContext(AbstractRefreshableApplicationContext ctx,
  749. boolean isOpenmrsStartup, Module startedModule) {
  750. //notify all started modules that we are about to refresh the context
  751. Set<Module> startedModules = new LinkedHashSet<Module>(ModuleFactory.getStartedModulesInOrder());
  752. for (Module module : startedModules) {
  753. try {
  754. if (module.getModuleActivator() != null)
  755. module.getModuleActivator().willRefreshContext();
  756. }
  757. catch (Throwable t) {
  758. log.warn("Unable to call willRefreshContext() method in the module's activator", t);
  759. }
  760. }
  761.  
  762. OpenmrsClassLoader.saveState();
  763. ServiceContext.destroyInstance();
  764.  
  765. try {
  766. ctx.stop();
  767. ctx.close();
  768. }
  769. catch (Exception e) {
  770. log.warn("Exception while stopping and closing context: ", e);
  771. // Spring seems to be trying to refresh the context instead of /just/ stopping
  772. // pass
  773. }
  774. OpenmrsClassLoader.destroyInstance();
  775. ctx.setClassLoader(OpenmrsClassLoader.getInstance());
  776. Thread.currentThread().setContextClassLoader(OpenmrsClassLoader.getInstance());
  777.  
  778. ServiceContext.getInstance().startRefreshingContext();
  779. try {
  780. ctx.refresh();
  781. }
  782. finally {
  783. ServiceContext.getInstance().doneRefreshingContext();
  784. }
  785.  
  786. ctx.setClassLoader(OpenmrsClassLoader.getInstance());
  787. Thread.currentThread().setContextClassLoader(OpenmrsClassLoader.getInstance());
  788.  
  789. OpenmrsClassLoader.restoreState();
  790.  
  791. // reload the advice points that were lost when refreshing Spring
  792. if (log.isDebugEnabled())
  793. log.debug("Reloading advice for all started modules: " + startedModules.size());
  794.  
  795. try {
  796. //The call backs in this block may need lazy loading of objects
  797. //which will fail because we use an OpenSessionInViewFilter whose opened session
  798. //was closed when the application context was refreshed as above.
  799. //So we need to open another session now. TRUNK-3739
  800. Context.openSessionWithCurrentUser();
  801. for (Module module : startedModules) {
  802. if (!module.isStarted()) {
  803. continue;
  804. }
  805.  
  806. ModuleFactory.loadAdvice(module);
  807. try {
  808. ModuleFactory.passDaemonToken(module);
  809.  
  810. if (module.getModuleActivator() != null) {
  811. module.getModuleActivator().contextRefreshed();
  812. try {
  813. //if it is system start up, call the started method for all started modules
  814. if (isOpenmrsStartup)
  815. module.getModuleActivator().started();
  816. //if refreshing the context after a user started or uploaded a new module
  817. else if (!isOpenmrsStartup && module.equals(startedModule))
  818. module.getModuleActivator().started();
  819. }
  820. catch (Exception e) {
  821. log.warn("Unable to invoke started() method on the module's activator", e);
  822. ModuleFactory.stopModule(module, true, true);
  823. }
  824. }
  825.  
  826. }
  827. catch (Throwable t) {
  828. log.warn("Unable to invoke method on the module's activator ", t);
  829. }
  830. }
  831. }
  832. finally {
  833. Context.closeSessionWithCurrentUser();
  834. }
  835.  
  836. return ctx;
  837. }
  838.  
  839. /**
  840. * Looks at the <moduleid>.mandatory properties and at the currently started modules to make
  841. * sure that all mandatory modules have been started successfully.
  842. *
  843. * @throws ModuleException if a mandatory module isn't started
  844. * @should throw ModuleException if a mandatory module is not started
  845. */
  846. protected static void checkMandatoryModulesStarted() throws ModuleException {
  847.  
  848. List<String> mandatoryModuleIds = getMandatoryModules();
  849. Set<String> startedModuleIds = ModuleFactory.getStartedModulesMap().keySet();
  850.  
  851. mandatoryModuleIds.removeAll(startedModuleIds);
  852.  
  853. // any module ids left in the list are not started
  854. if (mandatoryModuleIds.size() > 0) {
  855. throw new MandatoryModuleException(mandatoryModuleIds);
  856. }
  857. }
  858.  
  859. /**
  860. * Looks at the list of modules in {@link ModuleConstants#CORE_MODULES} to make sure that all
  861. * modules that are core to OpenMRS are started and have at least a minimum version that OpenMRS
  862. * needs.
  863. *
  864. * @throws ModuleException if a module that is core to OpenMRS is not started
  865. * @should throw ModuleException if a core module is not started
  866. */
  867. protected static void checkOpenmrsCoreModulesStarted() throws OpenmrsCoreModuleException {
  868.  
  869. // if there is a property telling us to ignore required modules, drop out early
  870. if (ignoreCoreModules())
  871. return;
  872.  
  873. // make a copy of the constant so we can modify the list
  874. Map<String, String> coreModules = new HashMap<String, String>(ModuleConstants.CORE_MODULES);
  875.  
  876. Collection<Module> startedModules = ModuleFactory.getStartedModulesMap().values();
  877.  
  878. // loop through the current modules and test them
  879. for (Module mod : startedModules) {
  880. String moduleId = mod.getModuleId();
  881. if (coreModules.containsKey(moduleId)) {
  882. String coreReqVersion = coreModules.get(moduleId);
  883. if (compareVersion(mod.getVersion(), coreReqVersion) >= 0)
  884. coreModules.remove(moduleId);
  885. else
  886. log.debug("Module: " + moduleId + " is a core module and is started, but its version: "
  887. + mod.getVersion() + " is not within the required version: " + coreReqVersion);
  888. }
  889. }
  890.  
  891. // any module ids left in the list are not started
  892. if (coreModules.size() > 0) {
  893. throw new OpenmrsCoreModuleException(coreModules);
  894. }
  895. }
  896.  
  897. /**
  898. * Uses the runtime properties to determine if the core modules should be enforced or not.
  899. *
  900. * @return true if the core modules list can be ignored.
  901. */
  902. public static boolean ignoreCoreModules() {
  903. String ignoreCoreModules = Context.getRuntimeProperties().getProperty(ModuleConstants.IGNORE_CORE_MODULES_PROPERTY,
  904. "false");
  905. return Boolean.parseBoolean(ignoreCoreModules);
  906. }
  907.  
  908. /**
  909. * Returns all modules that are marked as mandatory. Currently this means there is a
  910. * <moduleid>.mandatory=true global property.
  911. *
  912. * @return list of modules ids for mandatory modules
  913. * @should return mandatory module ids
  914. */
  915. public static List<String> getMandatoryModules() {
  916.  
  917. List<String> mandatoryModuleIds = new ArrayList<String>();
  918.  
  919. try {
  920. List<GlobalProperty> props = Context.getAdministrationService().getGlobalPropertiesBySuffix(".mandatory");
  921.  
  922. for (GlobalProperty prop : props) {
  923. if ("true".equalsIgnoreCase(prop.getPropertyValue())) {
  924. mandatoryModuleIds.add(prop.getProperty().replace(".mandatory", ""));
  925. }
  926. }
  927. }
  928. catch (Throwable t) {
  929. log.warn("Unable to get the mandatory module list", t);
  930. }
  931.  
  932. return mandatoryModuleIds;
  933. }
  934.  
  935. /**
  936. * <pre>
  937. * Gets the module that should handle a path. The path you pass in should be a module id (in
  938. * path format, i.e. /ui/springmvc, not ui.springmvc) followed by a resource. Something like
  939. * the following:
  940. * /ui/springmvc/css/ui.css
  941. *
  942. * The first running module out of the following would be returned:
  943. * ui.springmvc.css
  944. * ui.springmvc
  945. * ui
  946. * </pre>
  947. *
  948. * @param path
  949. * @return the running module that matches the most of the given path
  950. * @should handle ui springmvc css ui dot css when ui dot springmvc module is running
  951. * @should handle ui springmvc css ui dot css when ui module is running
  952. * @should return null for ui springmvc css ui dot css when no relevant module is running
  953. */
  954. public static Module getModuleForPath(String path) {
  955. int ind = path.lastIndexOf('/');
  956. if (ind <= 0) {
  957. throw new IllegalArgumentException(
  958. "Input must be /moduleId/resource. Input needs a / after the first character: " + path);
  959. }
  960. String moduleId = path.startsWith("/") ? path.substring(1, ind) : path.substring(0, ind);
  961. moduleId = moduleId.replace('/', '.');
  962. // iterate over progressively shorter module ids
  963. while (true) {
  964. Module mod = ModuleFactory.getStartedModuleById(moduleId);
  965. if (mod != null)
  966. return mod;
  967. // try the next shorter module id
  968. ind = moduleId.lastIndexOf('.');
  969. if (ind < 0)
  970. break;
  971. moduleId = moduleId.substring(0, ind);
  972. }
  973. return null;
  974. }
  975.  
  976. /**
  977. * Takes a global path and returns the local path within the specified module. For example
  978. * calling this method with the path "/ui/springmvc/css/ui.css" and the ui.springmvc module, you
  979. * would get "/css/ui.css".
  980. *
  981. * @param module
  982. * @param path
  983. * @return
  984. * @should handle ui springmvc css ui dot css example
  985. */
  986. public static String getPathForResource(Module module, String path) {
  987. if (path.startsWith("/"))
  988. path = path.substring(1);
  989. return path.substring(module.getModuleIdAsPath().length());
  990. }
  991.  
  992. /**
  993. * This loops over all FILES in this jar to get the package names. If there is an empty
  994. * directory in this jar it is not returned as a providedPackage.
  995. *
  996. * @param file jar file to look into
  997. * @return list of strings of package names in this jar
  998. */
  999. public static Collection<String> getPackagesFromFile(File file) {
  1000.  
  1001. // end early if we're given a non jar file
  1002. if (!file.getName().endsWith(".jar"))
  1003. return Collections.EMPTY_SET;
  1004.  
  1005. Set<String> packagesProvided = new HashSet<String>();
  1006.  
  1007. JarFile jar = null;
  1008. try {
  1009. jar = new JarFile(file);
  1010.  
  1011. Enumeration<JarEntry> jarEntries = jar.entries();
  1012. while (jarEntries.hasMoreElements()) {
  1013. JarEntry jarEntry = jarEntries.nextElement();
  1014. if (jarEntry.isDirectory()) {
  1015. // skip over directory entries, we only care about dirs with files in it
  1016. continue;
  1017. }
  1018. String name = jarEntry.getName();
  1019. Integer indexOfLastSlash = name.lastIndexOf("/");
  1020. if (indexOfLastSlash <= 0)
  1021. continue;
  1022. String packageName = name.substring(0, indexOfLastSlash);
  1023.  
  1024. // skip over some folders in the jar/omod
  1025. if (packageName.equals("lib") || packageName.equals("META-INF") || packageName.startsWith("web/module")) {
  1026. continue;
  1027. }
  1028.  
  1029. packageName = packageName.replaceAll("/", ".");
  1030.  
  1031. if (packagesProvided.add(packageName))
  1032. log.trace("Adding module's jarentry with package: " + packageName);
  1033. }
  1034.  
  1035. }
  1036. catch (IOException e) {
  1037. log.error("Unable to open jar from file: " + file.getAbsolutePath(), e);
  1038. }
  1039. finally {
  1040. if (jar != null)
  1041. try {
  1042. jar.close();
  1043. }
  1044. catch (IOException e) {
  1045. // do nothing
  1046. }
  1047.  
  1048. }
  1049.  
  1050. // clean up packages contained within other packages this is
  1051. // O(n^2), but its better than putting extra packages into the
  1052. // set and having the classloader continually loop over them
  1053. Set<String> packagesProvidedCopy = new HashSet<String>();
  1054. packagesProvidedCopy.addAll(packagesProvided);
  1055.  
  1056. for (String packageNameOuter : packagesProvidedCopy) {
  1057. // add the period so that we don't match to ourselves or to
  1058. // similarly named packages. eg. org.pih and org.pihrwanda
  1059. // should not match
  1060. packageNameOuter += ".";
  1061. for (String packageNameInner : packagesProvidedCopy) {
  1062. if (packageNameInner.contains(packageNameOuter)) {
  1063. packagesProvided.remove(packageNameInner);
  1064. }
  1065. }
  1066.  
  1067. }
  1068. // end cleanup
  1069.  
  1070. return packagesProvided;
  1071. }
  1072.  
  1073. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement