· 6 years ago · Apr 24, 2019, 11:26 PM
1/**
2 * This Source Code Form is subject to the terms of the Mozilla Public License,
3 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6 *
7 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8 * graphic logo is a trademark of OpenMRS Inc.
9 */
10package org.openmrs.util;
11
12import java.io.BufferedInputStream;
13import java.io.BufferedReader;
14import java.io.ByteArrayInputStream;
15import java.io.ByteArrayOutputStream;
16import java.io.Closeable;
17import java.io.File;
18import java.io.FileInputStream;
19import java.io.FileNotFoundException;
20import java.io.FileOutputStream;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.InputStreamReader;
24import java.io.OutputStream;
25import java.io.OutputStreamWriter;
26import java.io.UnsupportedEncodingException;
27import java.lang.reflect.Field;
28import java.lang.reflect.Method;
29import java.net.MalformedURLException;
30import java.net.URL;
31import java.nio.charset.Charset;
32import java.nio.charset.StandardCharsets;
33import java.sql.Timestamp;
34import java.text.DateFormat;
35import java.text.SimpleDateFormat;
36import java.util.ArrayList;
37import java.util.Arrays;
38import java.util.Calendar;
39import java.util.Collection;
40import java.util.Date;
41import java.util.Enumeration;
42import java.util.HashMap;
43import java.util.HashSet;
44import java.util.Iterator;
45import java.util.LinkedList;
46import java.util.List;
47import java.util.Locale;
48import java.util.Map;
49import java.util.Properties;
50import java.util.Random;
51import java.util.Set;
52import java.util.StringTokenizer;
53import java.util.jar.JarFile;
54import java.util.regex.Matcher;
55import java.util.regex.Pattern;
56import java.util.regex.PatternSyntaxException;
57import java.util.stream.Collectors;
58import java.util.zip.ZipEntry;
59
60import javax.activation.MimetypesFileTypeMap;
61import javax.xml.transform.OutputKeys;
62import javax.xml.transform.Transformer;
63import javax.xml.transform.TransformerException;
64import javax.xml.transform.TransformerFactory;
65import javax.xml.transform.dom.DOMSource;
66import javax.xml.transform.stream.StreamResult;
67
68import org.apache.commons.io.IOUtils;
69import org.apache.commons.lang3.StringUtils;
70import org.apache.log4j.Appender;
71import org.apache.log4j.FileAppender;
72import org.apache.log4j.Level;
73import org.apache.log4j.Logger;
74import org.apache.log4j.PatternLayout;
75import org.apache.log4j.RollingFileAppender;
76import org.openmrs.Cohort;
77import org.openmrs.Concept;
78import org.openmrs.ConceptNumeric;
79import org.openmrs.Drug;
80import org.openmrs.EncounterType;
81import org.openmrs.Form;
82import org.openmrs.Location;
83import org.openmrs.PersonAttributeType;
84import org.openmrs.Program;
85import org.openmrs.ProgramWorkflowState;
86import org.openmrs.User;
87import org.openmrs.annotation.AddOnStartup;
88import org.openmrs.annotation.HasAddOnStartupPrivileges;
89import org.openmrs.api.APIException;
90import org.openmrs.api.AdministrationService;
91import org.openmrs.api.ConceptService;
92import org.openmrs.api.InvalidCharactersPasswordException;
93import org.openmrs.api.PasswordException;
94import org.openmrs.api.ShortPasswordException;
95import org.openmrs.api.WeakPasswordException;
96import org.openmrs.api.context.Context;
97import org.openmrs.module.ModuleException;
98import org.openmrs.module.ModuleFactory;
99import org.openmrs.propertyeditor.CohortEditor;
100import org.openmrs.propertyeditor.ConceptEditor;
101import org.openmrs.propertyeditor.DrugEditor;
102import org.openmrs.propertyeditor.EncounterTypeEditor;
103import org.openmrs.propertyeditor.FormEditor;
104import org.openmrs.propertyeditor.LocationEditor;
105import org.openmrs.propertyeditor.PersonAttributeTypeEditor;
106import org.openmrs.propertyeditor.ProgramEditor;
107import org.openmrs.propertyeditor.ProgramWorkflowStateEditor;
108import org.slf4j.LoggerFactory;
109import org.slf4j.MarkerFactory;
110import org.springframework.beans.propertyeditors.CustomDateEditor;
111import org.springframework.context.ApplicationContextException;
112import org.springframework.context.NoSuchMessageException;
113import org.springframework.core.JdkVersion;
114import org.w3c.dom.Document;
115import org.w3c.dom.DocumentType;
116
117/**
118 * Utility methods used in openmrs
119 */
120public class OpenmrsUtil {
121
122 private OpenmrsUtil() {
123 }
124
125 private static org.slf4j.Logger log = LoggerFactory.getLogger(OpenmrsUtil.class);
126
127 private static Map<Locale, SimpleDateFormat> dateFormatCache = new HashMap<>();
128
129 private static Map<Locale, SimpleDateFormat> timeFormatCache = new HashMap<>();
130
131 /**
132 * Compares origList to newList returning map of differences
133 *
134 * @param origList
135 * @param newList
136 * @return [List toAdd, List toDelete] with respect to origList
137 */
138 public static <E> Collection<Collection<E>> compareLists(Collection<E> origList, Collection<E> newList) {
139 // TODO finish function
140
141 Collection<Collection<E>> returnList = new ArrayList<>();
142
143 Collection<E> toAdd = new LinkedList<>();
144 Collection<E> toDel = new LinkedList<>();
145
146 // loop over the new list.
147 for (E currentNewListObj : newList) {
148 // loop over the original list
149 boolean foundInList = false;
150 for (E currentOrigListObj : origList) {
151 // checking if the current new list object is in the original
152 // list
153 if (currentNewListObj.equals(currentOrigListObj)) {
154 foundInList = true;
155 origList.remove(currentOrigListObj);
156 break;
157 }
158 }
159 if (!foundInList) {
160 toAdd.add(currentNewListObj);
161 }
162
163 // all found new objects were removed from the orig list,
164 // leaving only objects needing to be removed
165 toDel = origList;
166
167 }
168
169 returnList.add(toAdd);
170 returnList.add(toDel);
171
172 return returnList;
173 }
174
175 public static boolean isStringInArray(String str, String[] arr) {
176 boolean retVal = false;
177
178 if (str != null && arr != null) {
179 for (String anArr : arr) {
180 if (str.equals(anArr)) {
181 retVal = true;
182 }
183 }
184 }
185 return retVal;
186 }
187
188 public static Boolean isInNormalNumericRange(Float value, ConceptNumeric concept) {
189 if (concept.getHiNormal() == null || concept.getLowNormal() == null) {
190 return false;
191 }
192 return (value <= concept.getHiNormal() && value >= concept.getLowNormal());
193 }
194
195 public static Boolean isInCriticalNumericRange(Float value, ConceptNumeric concept) {
196 if (concept.getHiCritical() == null || concept.getLowCritical() == null) {
197 return false;
198 }
199 return (value <= concept.getHiCritical() && value >= concept.getLowCritical());
200 }
201
202 public static Boolean isInAbsoluteNumericRange(Float value, ConceptNumeric concept) {
203 if (concept.getHiAbsolute() == null || concept.getLowAbsolute() == null) {
204 return false;
205 }
206 return (value <= concept.getHiAbsolute() && value >= concept.getLowAbsolute());
207 }
208
209 public static Boolean isValidNumericValue(Float value, ConceptNumeric concept) {
210 if (concept.getHiAbsolute() == null || concept.getLowAbsolute() == null) {
211 return true;
212 }
213 return (value <= concept.getHiAbsolute() && value >= concept.getLowAbsolute());
214 }
215
216 /**
217 * Return a string representation of the given file
218 *
219 * @param file
220 * @return String file contents
221 * @throws IOException
222 */
223 public static String getFileAsString(File file) throws IOException {
224 StringBuilder fileData = new StringBuilder(1000);
225 BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8));
226 char[] buf = new char[1024];
227 int numRead;
228 while ((numRead = reader.read(buf)) != -1) {
229 String readData = String.valueOf(buf, 0, numRead);
230 fileData.append(readData);
231 buf = new char[1024];
232 }
233 reader.close();
234 return fileData.toString();
235 }
236
237 /**
238 * Return a byte array representation of the given file
239 *
240 * @param file
241 * @return byte[] file contents
242 * @throws IOException
243 */
244 public static byte[] getFileAsBytes(File file) throws IOException {
245 FileInputStream fileInputStream = null;
246 try {
247 fileInputStream = new FileInputStream(file);
248 byte[] b = new byte[fileInputStream.available()];
249 fileInputStream.read(b);
250 return b;
251 }
252 catch (Exception e) {
253 log.error("Unable to get file as byte array", e);
254 }
255 finally {
256 if (fileInputStream != null) {
257 try {
258 fileInputStream.close();
259 }
260 catch (IOException io) {
261 log.warn("Couldn't close fileInputStream: " + io);
262 }
263 }
264 }
265
266 return null;
267 }
268
269 /**
270 * Copy file from inputStream onto the outputStream inputStream is not closed in this method
271 * outputStream /is/ closed at completion of this method
272 *
273 * @param inputStream Stream to copy from
274 * @param outputStream Stream/location to copy to
275 * @throws IOException thrown if an error occurs during read/write
276 * @should not copy the outputstream if outputstream is null
277 * @should not copy the outputstream if inputstream is null
278 * @should copy inputstream to outputstream and close the outputstream
279 */
280 public static void copyFile(InputStream inputStream, OutputStream outputStream) throws IOException {
281
282 if (inputStream == null || outputStream == null) {
283 if (outputStream != null) {
284 IOUtils.closeQuietly(outputStream);
285 }
286 return;
287 }
288
289 try {
290 IOUtils.copy(inputStream, outputStream);
291 }
292 finally {
293 IOUtils.closeQuietly(outputStream);
294 }
295
296 }
297
298 /**
299 * Get mime type of the given file
300 *
301 * @param file
302 * @return mime type
303 */
304 public static String getFileMimeType(File file) {
305 MimetypesFileTypeMap mimeMap = new MimetypesFileTypeMap();
306 return mimeMap.getContentType(file);
307 }
308
309 /**
310 * Look for a file named <code>filename</code> in folder
311 *
312 * @param folder
313 * @param filename
314 * @return true/false whether filename exists in folder
315 */
316 public static boolean folderContains(File folder, String filename) {
317 if (folder == null) {
318 return false;
319 }
320 if (!folder.isDirectory()) {
321 return false;
322 }
323
324 File[] files = folder.listFiles();
325 if (files == null) {
326 return false;
327 }
328
329 for (File f : files) {
330 if (f.getName().equals(filename)) {
331 return true;
332 }
333 }
334 return false;
335 }
336
337 /**
338 * These are the privileges that are required by OpenMRS. This looks for privileges marked as
339 * {@link AddOnStartup} to know which privs, upon startup or loading of a module, to insert into
340 * the database if they do not exist already. These privileges are not allowed to be deleted.
341 * They are marked as 'locked' in the administration screens.
342 *
343 * @return privileges core to the system
344 * @see PrivilegeConstants
345 * @see Context#checkCoreDataset()
346 */
347 public static Map<String, String> getCorePrivileges() {
348 Map<String, String> corePrivileges = new HashMap<>();
349
350 // TODO getCorePrivileges() is called so so many times that getClassesWithAnnotation() better do some catching.
351 Set<Class<?>> classes = OpenmrsClassScanner.getInstance().getClassesWithAnnotation(HasAddOnStartupPrivileges.class);
352
353 for (Class cls : classes) {
354 Field[] flds = cls.getDeclaredFields();
355 for (Field fld : flds) {
356 String fieldValue = null;
357
358 AddOnStartup privilegeAnnotation = fld.getAnnotation(AddOnStartup.class);
359 if (null == privilegeAnnotation) {
360 continue;
361 }
362 if (!privilegeAnnotation.core()) {
363 continue;
364 }
365
366 try {
367 fieldValue = (String) fld.get(null);
368 }
369 catch (IllegalAccessException e) {
370 log.error("Field is inaccessible.", e);
371 }
372 corePrivileges.put(fieldValue, privilegeAnnotation.description());
373 }
374 }
375
376 // always add the module core privileges back on
377 for (org.openmrs.Privilege privilege : ModuleFactory.getPrivileges()) {
378 corePrivileges.put(privilege.getPrivilege(), privilege.getDescription());
379 }
380
381 return corePrivileges;
382 }
383
384 /**
385 * All roles returned by this method are inserted into the database if they do not exist
386 * already. These roles are also forbidden to be deleted from the administration screens.
387 *
388 * @return roles that are core to the system
389 */
390 public static Map<String, String> getCoreRoles() {
391 Map<String, String> roles = new HashMap<>();
392
393 Field[] flds = RoleConstants.class.getDeclaredFields();
394 for (Field fld : flds) {
395 String fieldValue = null;
396
397 AddOnStartup roleAnnotation = fld.getAnnotation(AddOnStartup.class);
398 if (null == roleAnnotation) {
399 continue;
400 }
401 if (!roleAnnotation.core()) {
402 continue;
403 }
404
405 try {
406 fieldValue = (String) fld.get(null);
407 }
408 catch (IllegalAccessException e) {
409 log.error("Field is inaccessible.", e);
410 }
411 roles.put(fieldValue, roleAnnotation.description());
412 }
413
414 return roles;
415 }
416
417 /**
418 * Initialize global settings Find and load modules
419 *
420 * @param p properties from runtime configuration
421 */
422 public static void startup(Properties p) {
423
424 // Override global OpenMRS constants if specified by the user
425
426 // Allow for "demo" mode where patient data is obscured
427 String val = p.getProperty("obscure_patients", null);
428 if (val != null && "true".equalsIgnoreCase(val)) {
429 OpenmrsConstants.OBSCURE_PATIENTS = true;
430 }
431
432 val = p.getProperty("obscure_patients.family_name", null);
433 if (val != null) {
434 OpenmrsConstants.OBSCURE_PATIENTS_FAMILY_NAME = val;
435 }
436
437 val = p.getProperty("obscure_patients.given_name", null);
438 if (val != null) {
439 OpenmrsConstants.OBSCURE_PATIENTS_GIVEN_NAME = val;
440 }
441
442 val = p.getProperty("obscure_patients.middle_name", null);
443 if (val != null) {
444 OpenmrsConstants.OBSCURE_PATIENTS_MIDDLE_NAME = val;
445 }
446
447 // Override the default "openmrs" database name
448 val = p.getProperty("connection.database_name", null);
449 if (val == null) {
450 // the database name wasn't supplied explicitly, guess it
451 // from the connection string
452 val = p.getProperty("connection.url", null);
453
454 if (val != null) {
455 try {
456 int endIndex = val.lastIndexOf("?");
457 if (endIndex == -1) {
458 endIndex = val.length();
459 }
460 int startIndex = val.lastIndexOf("/", endIndex);
461 val = val.substring(startIndex + 1, endIndex);
462 OpenmrsConstants.DATABASE_NAME = val;
463 }
464 catch (Exception e) {
465 log.error(MarkerFactory.getMarker("FATAL"), "Database name cannot be configured from 'connection.url' ."
466 + "Either supply 'connection.database_name' or correct the url",
467 e);
468 }
469 }
470 }
471
472 // set the business database name
473 val = p.getProperty("connection.database_business_name", null);
474 if (val == null) {
475 val = OpenmrsConstants.DATABASE_NAME;
476 }
477 OpenmrsConstants.DATABASE_BUSINESS_NAME = val;
478
479 // set global log level
480 applyLogLevels();
481
482 setupLogAppenders();
483 }
484
485 /**
486 * Set the org.openmrs log4j logger's level if global property log.level.openmrs (
487 * OpenmrsConstants.GLOBAL_PROPERTY_LOG_LEVEL ) exists. Valid values for global property are
488 * trace, debug, info, warn, error or fatal.
489 */
490 public static void applyLogLevels() {
491 AdministrationService adminService = Context.getAdministrationService();
492 String logLevel = adminService.getGlobalProperty(OpenmrsConstants.GLOBAL_PROPERTY_LOG_LEVEL, "");
493
494 String[] levels = logLevel.split(",");
495 for (String level : levels) {
496 String[] classAndLevel = level.split(":");
497 if (classAndLevel.length == 1) {
498 applyLogLevel(OpenmrsConstants.LOG_CLASS_DEFAULT, logLevel);
499 } else {
500 applyLogLevel(classAndLevel[0].trim(), classAndLevel[1].trim());
501 }
502 }
503 }
504
505 /**
506 * Setup root level log appenders.
507 *
508 * @since 1.9.2
509 */
510 public static void setupLogAppenders() {
511 Logger rootLogger = Logger.getRootLogger();
512
513 FileAppender fileAppender = null;
514 @SuppressWarnings("rawtypes")
515 Enumeration appenders = rootLogger.getAllAppenders();
516 while (appenders.hasMoreElements()) {
517 Appender appender = (Appender) appenders.nextElement();
518 if (OpenmrsConstants.LOG_OPENMRS_FILE_APPENDER.equals(appender.getName())) {
519 fileAppender = (FileAppender) appender; //the appender already exists
520 break;
521 }
522 }
523
524 String logLayout = Context.getAdministrationService().getGlobalProperty(OpenmrsConstants.GP_LOG_LAYOUT,
525 "%p - %C{1}.%M(%L) |%d{ISO8601}| %m%n");
526 PatternLayout patternLayout = new PatternLayout(logLayout);
527
528 String logLocation = null;
529 try {
530 logLocation = OpenmrsUtil.getOpenmrsLogLocation();
531 if (fileAppender == null) {
532 fileAppender = new RollingFileAppender(patternLayout, logLocation);
533 fileAppender.setName(OpenmrsConstants.LOG_OPENMRS_FILE_APPENDER);
534 rootLogger.addAppender(fileAppender);
535 } else {
536 fileAppender.setFile(logLocation);
537 fileAppender.setLayout(patternLayout);
538 }
539 fileAppender.activateOptions();
540 }
541 catch (IOException e) {
542 log.error("Error while setting an OpenMRS log file to " + logLocation, e);
543 }
544 }
545
546 /**
547 * Set the log4j log level for class <code>logClass</code> to <code>logLevel</code>.
548 *
549 * @param logClass optional string giving the class level to change. Defaults to
550 * OpenmrsConstants.LOG_CLASS_DEFAULT . Should be something like org.openmrs.___
551 * @param logLevel one of OpenmrsConstants.LOG_LEVEL_*
552 */
553 public static void applyLogLevel(String logClass, String logLevel) {
554
555 if (logLevel != null) {
556
557 // the default log level is org.openmrs
558 if (StringUtils.isEmpty(logClass)) {
559 logClass = OpenmrsConstants.LOG_CLASS_DEFAULT;
560 }
561
562 Logger logger = Logger.getLogger(logClass);
563
564 logLevel = logLevel.toLowerCase();
565 if (OpenmrsConstants.LOG_LEVEL_TRACE.equals(logLevel)) {
566 logger.setLevel(Level.TRACE);
567 } else if (OpenmrsConstants.LOG_LEVEL_DEBUG.equals(logLevel)) {
568 logger.setLevel(Level.DEBUG);
569 } else if (OpenmrsConstants.LOG_LEVEL_INFO.equals(logLevel)) {
570 logger.setLevel(Level.INFO);
571 } else if (OpenmrsConstants.LOG_LEVEL_WARN.equals(logLevel)) {
572 logger.setLevel(Level.WARN);
573 } else if (OpenmrsConstants.LOG_LEVEL_ERROR.equals(logLevel)) {
574 logger.setLevel(Level.ERROR);
575 } else if (OpenmrsConstants.LOG_LEVEL_FATAL.equals(logLevel)) {
576 logger.setLevel(Level.FATAL);
577 } else {
578 log.warn("Global property " + logLevel + " is invalid. "
579 + "Valid values are trace, debug, info, warn, error or fatal");
580 }
581 }
582 }
583
584 /**
585 * Takes a String like "size=compact|order=date" and returns a Map<String,String> from the
586 * keys to the values.
587 *
588 * @param paramList <code>String</code> with a list of parameters
589 * @return Map<String, String> of the parameters passed
590 */
591 public static Map<String, String> parseParameterList(String paramList) {
592 Map<String, String> ret = new HashMap<>();
593 if (paramList != null && paramList.length() > 0) {
594 String[] args = paramList.split("\\|");
595 for (String s : args) {
596 int ind = s.indexOf('=');
597 if (ind <= 0) {
598 throw new IllegalArgumentException(
599 "Misformed argument in dynamic page specification string: '" + s + "' is not 'key=value'.");
600 }
601 String name = s.substring(0, ind);
602 String value = s.substring(ind + 1);
603 ret.put(name, value);
604 }
605 }
606 return ret;
607 }
608
609 public static <Arg1, Arg2 extends Arg1> boolean nullSafeEquals(Arg1 d1, Arg2 d2) {
610 if (d1 == null) {
611 return d2 == null;
612 } else if (d2 == null) {
613 return false;
614 }
615 return (d1 instanceof Date && d2 instanceof Date) ? compare((Date) d1, (Date) d2) == 0 : d1.equals(d2);
616 }
617
618 /**
619 * Compares two java.util.Date objects, but handles java.sql.Timestamp (which is not directly
620 * comparable to a date) by dropping its nanosecond value.
621 */
622 public static int compare(Date d1, Date d2) {
623 if (d1 instanceof Timestamp && d2 instanceof Timestamp) {
624 return d1.compareTo(d2);
625 }
626 if (d1 instanceof Timestamp) {
627 d1 = new Date(d1.getTime());
628 }
629 if (d2 instanceof Timestamp) {
630 d2 = new Date(d2.getTime());
631 }
632 return d1.compareTo(d2);
633 }
634
635 /**
636 * Compares two Date/Timestamp objects, treating null as the earliest possible date.
637 */
638 public static int compareWithNullAsEarliest(Date d1, Date d2) {
639 if (d1 == null && d2 == null) {
640 return 0;
641 }
642 if (d1 == null) {
643 return -1;
644 } else if (d2 == null) {
645 return 1;
646 } else {
647 return compare(d1, d2);
648 }
649 }
650
651 /**
652 * Compares two Date/Timestamp objects, treating null as the earliest possible date.
653 */
654 public static int compareWithNullAsLatest(Date d1, Date d2) {
655 if (d1 == null && d2 == null) {
656 return 0;
657 }
658 if (d1 == null) {
659 return 1;
660 } else if (d2 == null) {
661 return -1;
662 } else {
663 return compare(d1, d2);
664 }
665 }
666
667 public static <E extends Comparable<E>> int compareWithNullAsLowest(E c1, E c2) {
668 if (c1 == null && c2 == null) {
669 return 0;
670 }
671 if (c1 == null) {
672 return -1;
673 } else if (c2 == null) {
674 return 1;
675 } else {
676 return c1.compareTo(c2);
677 }
678 }
679
680 public static <E extends Comparable<E>> int compareWithNullAsGreatest(E c1, E c2) {
681 if (c1 == null && c2 == null) {
682 return 0;
683 }
684 if (c1 == null) {
685 return 1;
686 } else if (c2 == null) {
687 return -1;
688 } else {
689 return c1.compareTo(c2);
690 }
691 }
692
693 /**
694 * Converts a collection to a String with a specified separator between all elements
695 *
696 * @param c Collection to be joined
697 * @param separator string to put between all elements
698 * @return a String representing the toString() of all elements in c, separated by separator
699 * @deprecated as of 2.2 use Java's {@link String#join} or Apache Commons StringUtils.join for iterables which do not extend {@link CharSequence}
700 */
701 @Deprecated
702 public static <E> String join(Collection<E> c, String separator) {
703 if (c == null) {
704 return "";
705 }
706
707 StringBuilder ret = new StringBuilder();
708 for (Iterator<E> i = c.iterator(); i.hasNext();) {
709 ret.append(i.next());
710 if (i.hasNext()) {
711 ret.append(separator);
712 }
713 }
714 return ret.toString();
715 }
716
717 public static Set<Concept> conceptSetHelper(String descriptor) {
718 Set<Concept> ret = new HashSet<>();
719 if (descriptor == null || descriptor.length() == 0) {
720 return ret;
721 }
722 ConceptService cs = Context.getConceptService();
723
724 for (StringTokenizer st = new StringTokenizer(descriptor, "|"); st.hasMoreTokens();) {
725 String s = st.nextToken().trim();
726 boolean isSet = s.startsWith("set:");
727 if (isSet) {
728 s = s.substring(4).trim();
729 }
730 Concept c = null;
731 if (s.startsWith("name:")) {
732 String name = s.substring(5).trim();
733 c = cs.getConceptByName(name);
734 } else {
735 try {
736 c = cs.getConcept(Integer.valueOf(s.trim()));
737 }
738 catch (Exception ex) {}
739 }
740 if (c != null) {
741 if (isSet) {
742 List<Concept> inSet = cs.getConceptsByConceptSet(c);
743 ret.addAll(inSet);
744 } else {
745 ret.add(c);
746 }
747 }
748 }
749 return ret;
750 }
751
752 /**
753 * Parses and loads a delimited list of concept ids or names
754 *
755 * @param delimitedString the delimited list of concept ids or names
756 * @param delimiter the delimiter, e.g. ","
757 * @return the list of concepts
758 * @since 1.10, 1.9.2, 1.8.5
759 */
760 public static List<Concept> delimitedStringToConceptList(String delimitedString, String delimiter) {
761 List<Concept> ret = null;
762
763 if (delimitedString != null) {
764 String[] tokens = delimitedString.split(delimiter);
765 for (String token : tokens) {
766 Integer conceptId;
767
768 try {
769 conceptId = Integer.valueOf(token);
770 }
771 catch (NumberFormatException nfe) {
772 conceptId = null;
773 }
774
775 Concept c;
776
777 if (conceptId != null) {
778 c = Context.getConceptService().getConcept(conceptId);
779 } else {
780 c = Context.getConceptService().getConceptByName(token);
781 }
782
783 if (c != null) {
784 if (ret == null) {
785 ret = new ArrayList<>();
786 }
787 ret.add(c);
788 }
789 }
790 }
791
792 return ret;
793 }
794
795 public static Map<String, Concept> delimitedStringToConceptMap(String delimitedString, String delimiter) {
796 Map<String, Concept> ret = null;
797
798 if (delimitedString != null) {
799 String[] tokens = delimitedString.split(delimiter);
800 for (String token : tokens) {
801 Concept c = Context.getConceptService().getConcept(token);
802
803 if (c != null) {
804 if (ret == null) {
805 ret = new HashMap<>();
806 }
807 ret.put(token, c);
808 }
809 }
810 }
811
812 return ret;
813 }
814
815 // TODO: properly handle duplicates
816 public static List<Concept> conceptListHelper(String descriptor) {
817 List<Concept> ret = new ArrayList<>();
818 if (descriptor == null || descriptor.length() == 0) {
819 return ret;
820 }
821 ConceptService cs = Context.getConceptService();
822
823 for (StringTokenizer st = new StringTokenizer(descriptor, "|"); st.hasMoreTokens();) {
824 String s = st.nextToken().trim();
825 boolean isSet = s.startsWith("set:");
826 if (isSet) {
827 s = s.substring(4).trim();
828 }
829 Concept c = null;
830 if (s.startsWith("name:")) {
831 String name = s.substring(5).trim();
832 c = cs.getConceptByName(name);
833 } else {
834 try {
835 c = cs.getConcept(Integer.valueOf(s.trim()));
836 }
837 catch (Exception ex) {}
838 }
839 if (c != null) {
840 if (isSet) {
841 List<Concept> inSet = cs.getConceptsByConceptSet(c);
842 ret.addAll(inSet);
843 } else {
844 ret.add(c);
845 }
846 }
847 }
848 return ret;
849 }
850
851 /**
852 * Gets the date having the last millisecond of a given day. Meaning that the hours, seconds,
853 * and milliseconds are the latest possible for that day.
854 *
855 * @param day the day.
856 * @return the date with the last millisecond of the day.
857 */
858 public static Date getLastMomentOfDay(Date day) {
859 Calendar calender = Calendar.getInstance();
860 calender.setTime(day);
861 calender.set(Calendar.HOUR_OF_DAY, 23);
862 calender.set(Calendar.MINUTE, 59);
863 calender.set(Calendar.SECOND, 59);
864 calender.set(Calendar.MILLISECOND, 999);
865
866 return calender.getTime();
867 }
868
869 /**
870 * Return a date that is the same day as the passed in date, but the hours and seconds are the
871 * earliest possible for that day.
872 *
873 * @param date date to adjust
874 * @return a date that is the first possible time in the day
875 * @since 1.9
876 */
877 public static Date firstSecondOfDay(Date date) {
878 if (date == null) {
879 return null;
880 }
881
882 Calendar c = Calendar.getInstance();
883 c.setTime(date);
884 c.set(Calendar.HOUR_OF_DAY, 0);
885 c.set(Calendar.MINUTE, 0);
886 c.set(Calendar.SECOND, 0);
887 c.set(Calendar.MILLISECOND, 0);
888
889 return c.getTime();
890 }
891
892 public static Date safeDate(Date d1) {
893 return new Date(d1.getTime());
894 }
895
896 /**
897 * Recursively deletes files in the given <code>dir</code> folder
898 *
899 * @param dir File directory to delete
900 * @return true/false whether the delete was completed successfully
901 * @throws IOException if <code>dir</code> is not a directory
902 */
903 public static boolean deleteDirectory(File dir) throws IOException {
904 if (!dir.exists() || !dir.isDirectory()) {
905 throw new IOException("Could not delete directory '" + dir.getAbsolutePath() + "' (not a directory)");
906 }
907
908 if (log.isDebugEnabled()) {
909 log.debug("Deleting directory " + dir.getAbsolutePath());
910 }
911
912 File[] fileList = dir.listFiles();
913 if (fileList == null) {
914 return false;
915 }
916
917 for (File f : fileList) {
918 if (f.isDirectory()) {
919 deleteDirectory(f);
920 }
921 boolean success = f.delete();
922
923 if (log.isDebugEnabled()) {
924 log.debug(" deleting " + f.getName() + " : " + (success ? "ok" : "failed"));
925 }
926
927 if (!success) {
928 f.deleteOnExit();
929 }
930 }
931
932 boolean success = dir.delete();
933
934 if (!success) {
935 log.warn(" ...could not remove directory: " + dir.getAbsolutePath());
936 dir.deleteOnExit();
937 }
938
939 if (success && log.isDebugEnabled()) {
940 log.debug(" ...and directory itself");
941 }
942
943 return success;
944 }
945
946 /**
947 * Utility method to convert local URL to a File object.
948 *
949 * @param url an URL
950 * @return file object for given URL or <code>null</code> if URL is not local
951 * @should return null given null parameter
952 */
953 public static File url2file(final URL url) {
954 if (url == null || !"file".equalsIgnoreCase(url.getProtocol())) {
955 return null;
956 }
957 return new File(url.getFile().replaceAll("%20", " "));
958 }
959
960 /**
961 * Opens input stream for given resource. This method behaves differently for different URL
962 * types:
963 * <ul>
964 * <li>for <b>local files</b> it returns buffered file input stream;</li>
965 * <li>for <b>local JAR files</b> it reads resource content into memory buffer and returns byte
966 * array input stream that wraps those buffer (this prevents locking JAR file);</li>
967 * <li>for <b>common URL's</b> this method simply opens stream to that URL using standard URL
968 * API.</li>
969 * </ul>
970 * It is not recommended to use this method for big resources within JAR files.
971 *
972 * @param url resource URL
973 * @return input stream for given resource
974 * @throws IOException if any I/O error has occurred
975 */
976 public static InputStream getResourceInputStream(final URL url) throws IOException {
977 File file = url2file(url);
978 if (file != null) {
979 return new BufferedInputStream(new FileInputStream(file));
980 }
981 if (!"jar".equalsIgnoreCase(url.getProtocol())) {
982 return url.openStream();
983 }
984 String urlStr = url.toExternalForm();
985 if (urlStr.endsWith("!/")) {
986 // JAR URL points to a root entry
987 throw new FileNotFoundException(url.toExternalForm());
988 }
989 int p = urlStr.indexOf("!/");
990 if (p == -1) {
991 throw new MalformedURLException(url.toExternalForm());
992 }
993 String path = urlStr.substring(p + 2);
994 file = url2file(new URL(urlStr.substring(4, p)));
995 if (file == null) {// non-local JAR file URL
996 return url.openStream();
997 }
998 try (JarFile jarFile = new JarFile(file)) {
999 ZipEntry entry = jarFile.getEntry(path);
1000 if (entry == null) {
1001 throw new FileNotFoundException(url.toExternalForm());
1002 }
1003 try (InputStream in = jarFile.getInputStream(entry)) {
1004 ByteArrayOutputStream out = new ByteArrayOutputStream();
1005 copyFile(in, out);
1006 return new ByteArrayInputStream(out.toByteArray());
1007 }
1008 }
1009 }
1010
1011 /**
1012 * <pre>
1013 * Returns the application data directory. Searches for the value first
1014 * in the "OPENMRS_APPLICATION_DATA_DIRECTORY" system property and "application_data_directory" runtime property, then in the servlet
1015 * init parameter "application.data.directory." If not found, returns:
1016 * a) "{user.home}/.OpenMRS" on UNIX-based systems
1017 * b) "{user.home}\Application Data\OpenMRS" on Windows
1018 *
1019 * </pre>
1020 *
1021 * @return The path to the directory on the file system that will hold miscellaneous data about
1022 * the application (runtime properties, modules, etc)
1023 */
1024 public static String getApplicationDataDirectory() {
1025 String filepath = null;
1026 final String openmrsDir = "OpenMRS";
1027
1028 String systemProperty = System.getProperty(OpenmrsConstants.KEY_OPENMRS_APPLICATION_DATA_DIRECTORY);
1029 //System and runtime property take precedence
1030 if (StringUtils.isNotBlank(systemProperty)) {
1031 filepath = systemProperty;
1032 } else {
1033 String runtimeProperty = Context.getRuntimeProperties()
1034 .getProperty(OpenmrsConstants.APPLICATION_DATA_DIRECTORY_RUNTIME_PROPERTY, null);
1035 if (StringUtils.isNotBlank(runtimeProperty)) {
1036 filepath = runtimeProperty;
1037 }
1038 }
1039
1040 if (filepath == null) {
1041 if (OpenmrsConstants.UNIX_BASED_OPERATING_SYSTEM) {
1042 filepath = System.getProperty("user.home") + File.separator + "." + openmrsDir;
1043 if (!canWrite(new File(filepath))) {
1044 log.warn("Unable to write to users home dir, fallback to: "
1045 + OpenmrsConstants.APPLICATION_DATA_DIRECTORY_FALLBACK_UNIX);
1046 filepath = OpenmrsConstants.APPLICATION_DATA_DIRECTORY_FALLBACK_UNIX + File.separator + openmrsDir;
1047 }
1048 } else {
1049 filepath = System.getProperty("user.home") + File.separator + "Application Data" + File.separator
1050 + "OpenMRS";
1051 if (!canWrite(new File(filepath))) {
1052 log.warn("Unable to write to users home dir, fallback to: "
1053 + OpenmrsConstants.APPLICATION_DATA_DIRECTORY_FALLBACK_WIN);
1054 filepath = OpenmrsConstants.APPLICATION_DATA_DIRECTORY_FALLBACK_WIN + File.separator + openmrsDir;
1055 }
1056 }
1057
1058 filepath = filepath + File.separator;
1059 }
1060
1061 File folder = new File(filepath);
1062 if (!folder.exists()) {
1063 folder.mkdirs();
1064 }
1065
1066 return filepath;
1067 }
1068
1069 /**
1070 * Can be used to override default application data directory.
1071 * <p>
1072 * Note that it will not override application data directory provided as a system property.
1073 *
1074 * @param path
1075 * @since 1.11
1076 */
1077 public static void setApplicationDataDirectory(String path) {
1078 if (StringUtils.isBlank(path)) {
1079 System.clearProperty(OpenmrsConstants.KEY_OPENMRS_APPLICATION_DATA_DIRECTORY);
1080 } else {
1081 System.setProperty(OpenmrsConstants.KEY_OPENMRS_APPLICATION_DATA_DIRECTORY, path);
1082 }
1083 }
1084
1085 /**
1086 * Checks if we can write to a given folder.
1087 *
1088 * @param folder the directory to check.
1089 * @return true if we can write to it, else false.
1090 */
1091 private static boolean canWrite(File folder) {
1092 try {
1093 //We need to first create the folder if it does not exist,
1094 //else File.canWrite() will return false even when we
1095 //have the necessary permissions.
1096 if (!folder.exists()) {
1097 folder.mkdirs();
1098 }
1099
1100 return folder.canWrite();
1101 }
1102 catch (SecurityException ex) {
1103 //all we wanted to know is whether we have permissions
1104 }
1105
1106 return false;
1107 }
1108
1109 /**
1110 * Returns the location of the OpenMRS log file.
1111 *
1112 * @return the path to the OpenMRS log file
1113 * @since 1.9.2
1114 */
1115 public static String getOpenmrsLogLocation() {
1116 String logPathGP = Context.getAdministrationService().getGlobalProperty(OpenmrsConstants.GP_LOG_LOCATION, "");
1117 File logPath = OpenmrsUtil.getDirectoryInApplicationDataDirectory(logPathGP);
1118
1119 File logFile = new File(logPath, "openmrs.log");
1120 return logFile.getPath();
1121 }
1122
1123 /**
1124 * Checks whether the current JVM version is at least Java 6.
1125 *
1126 * @throws ApplicationContextException if the current JVM version is earlier than Java 6
1127 */
1128 public static void validateJavaVersion() {
1129 // check whether the current JVM version is at least Java 6
1130 if (JdkVersion.getJavaVersion().matches("1.(0|1|2|3|4|5).(.*)")) {
1131 throw new APIException("OpenMRS requires Java 6, but is running under " + JdkVersion.getJavaVersion());
1132 }
1133 }
1134
1135 /**
1136 * Find the given folderName in the application data directory. Or, treat folderName like an
1137 * absolute url to a directory
1138 *
1139 * @param folderName
1140 * @return folder capable of storing information
1141 */
1142 public static File getDirectoryInApplicationDataDirectory(String folderName) throws APIException {
1143 // try to load the repository folder straight away.
1144 File folder = new File(folderName);
1145
1146 // if the property wasn't a full path already, assume it was intended to
1147 // be a folder in the
1148 // application directory
1149 if (!folder.isAbsolute()) {
1150 folder = new File(OpenmrsUtil.getApplicationDataDirectory(), folderName);
1151 }
1152
1153 // now create the directory folder if it doesn't exist
1154 if (!folder.exists()) {
1155 log.warn("'" + folder.getAbsolutePath() + "' doesn't exist. Creating directories now.");
1156 folder.mkdirs();
1157 }
1158
1159 if (!folder.isDirectory()) {
1160 throw new APIException("should.be.directory", new Object[] { folder.getAbsolutePath() });
1161 }
1162
1163 return folder;
1164 }
1165
1166 /**
1167 * Save the given xml document to the given outfile
1168 *
1169 * @param doc Document to be saved
1170 * @param outFile file pointer to the location the xml file is to be saved to
1171 */
1172 public static void saveDocument(Document doc, File outFile) {
1173 OutputStream outStream = null;
1174 try {
1175 outStream = new FileOutputStream(outFile);
1176 TransformerFactory tFactory = TransformerFactory.newInstance();
1177 Transformer transformer = tFactory.newTransformer();
1178 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
1179
1180 DocumentType doctype = doc.getDoctype();
1181 if (doctype != null) {
1182 transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, doctype.getPublicId());
1183 transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doctype.getSystemId());
1184 }
1185
1186 DOMSource source = new DOMSource(doc);
1187 StreamResult result = new StreamResult(outStream);
1188 transformer.transform(source, result);
1189 }
1190 catch (TransformerException e) {
1191 throw new ModuleException("Error while saving dwrmodulexml back to dwr-modules.xml", e);
1192 }
1193 catch (FileNotFoundException e) {
1194 throw new ModuleException(outFile.getAbsolutePath() + " file doesn't exist.", e);
1195 }
1196 finally {
1197 try {
1198 if (outStream != null) {
1199 outStream.close();
1200 }
1201 }
1202 catch (Exception e) {
1203 log.warn("Unable to close outstream", e);
1204 }
1205 }
1206 }
1207
1208 public static List<Integer> delimitedStringToIntegerList(String delimitedString, String delimiter) {
1209 List<Integer> ret = new ArrayList<>();
1210 String[] tokens = delimitedString.split(delimiter);
1211 for (String token : tokens) {
1212 token = token.trim();
1213 if (token.length() != 0) {
1214 ret.add(Integer.valueOf(token));
1215 }
1216 }
1217 return ret;
1218 }
1219
1220 /**
1221 * Tests if the given String starts with any of the specified prefixes
1222 *
1223 * @param str the string to test
1224 * @param prefixes an array of prefixes to test against
1225 * @return true if the String starts with any of the specified prefixes, otherwise false.
1226 */
1227 public static boolean stringStartsWith(String str, String[] prefixes) {
1228 for (String prefix : prefixes) {
1229 if (StringUtils.startsWith(str, prefix)) {
1230 return true;
1231 }
1232 }
1233
1234 return false;
1235 }
1236
1237 public static boolean isConceptInList(Concept concept, List<Concept> list) {
1238 boolean ret = false;
1239 if (concept != null && list != null) {
1240 for (Concept c : list) {
1241 if (c.equals(concept)) {
1242 ret = true;
1243 break;
1244 }
1245 }
1246 }
1247
1248 return ret;
1249 }
1250
1251 public static Date fromDateHelper(Date comparisonDate, Integer withinLastDays, Integer withinLastMonths,
1252 Integer untilDaysAgo, Integer untilMonthsAgo, Date sinceDate, Date untilDate) {
1253
1254 Date ret = null;
1255 if (withinLastDays != null || withinLastMonths != null) {
1256 Calendar gc = Calendar.getInstance();
1257 gc.setTime(comparisonDate != null ? comparisonDate : new Date());
1258 if (withinLastDays != null) {
1259 gc.add(Calendar.DAY_OF_MONTH, -withinLastDays);
1260 }
1261 if (withinLastMonths != null) {
1262 gc.add(Calendar.MONTH, -withinLastMonths);
1263 }
1264 ret = gc.getTime();
1265 }
1266 if (sinceDate != null && (ret == null || sinceDate.after(ret))) {
1267 ret = sinceDate;
1268 }
1269 return ret;
1270 }
1271
1272 public static Date toDateHelper(Date comparisonDate, Integer withinLastDays, Integer withinLastMonths,
1273 Integer untilDaysAgo, Integer untilMonthsAgo, Date sinceDate, Date untilDate) {
1274
1275 Date ret = null;
1276 if (untilDaysAgo != null || untilMonthsAgo != null) {
1277 Calendar gc = Calendar.getInstance();
1278 gc.setTime(comparisonDate != null ? comparisonDate : new Date());
1279 if (untilDaysAgo != null) {
1280 gc.add(Calendar.DAY_OF_MONTH, -untilDaysAgo);
1281 }
1282 if (untilMonthsAgo != null) {
1283 gc.add(Calendar.MONTH, -untilMonthsAgo);
1284 }
1285 ret = gc.getTime();
1286 }
1287 if (untilDate != null && (ret == null || untilDate.before(ret))) {
1288 ret = untilDate;
1289 }
1290 return ret;
1291 }
1292
1293 /**
1294 * @param collection
1295 * @param elements
1296 * @return Whether _collection_ contains any of _elements_
1297 */
1298 public static <T> boolean containsAny(Collection<T> collection, Collection<T> elements) {
1299 for (T obj : elements) {
1300 if (collection.contains(obj)) {
1301 return true;
1302 }
1303 }
1304 return false;
1305 }
1306
1307 /**
1308 * Allows easy manipulation of a Map<?, Set>
1309 */
1310 public static <K, V> void addToSetMap(Map<K, Set<V>> map, K key, V obj) {
1311 Set<V> set = map.computeIfAbsent(key, k -> new HashSet<>());
1312 set.add(obj);
1313 }
1314
1315 public static <K, V> void addToListMap(Map<K, List<V>> map, K key, V obj) {
1316 List<V> list = map.computeIfAbsent(key, k -> new ArrayList<>());
1317 list.add(obj);
1318 }
1319
1320 /**
1321 * Get the current user's date format Will look similar to "mm-dd-yyyy". Depends on user's
1322 * locale.
1323 *
1324 * @return a simple date format
1325 * @should return a pattern with four y characters in it
1326 * @should not allow the returned SimpleDateFormat to be modified
1327 * @since 1.5
1328 */
1329 public static DateFormat getDateFormat(Locale locale) {
1330 if (dateFormatCache.containsKey(locale)) {
1331 return (SimpleDateFormat) dateFormatCache.get(locale).clone();
1332 }
1333
1334 // note that we are using the custom OpenmrsDateFormat class here which prevents erroneous parsing of 2-digit years
1335 SimpleDateFormat sdf = new OpenmrsDateFormat ((SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, locale),
1336 locale);
1337 String pattern = sdf.toPattern();
1338
1339 if (!pattern.contains("yyyy")) {
1340 // otherwise, change the pattern to be a four digit year
1341 pattern = pattern.replaceFirst("yy", "yyyy");
1342 sdf.applyPattern(pattern);
1343 }
1344 if (!pattern.contains("MM")) {
1345 // change the pattern to be a two digit month
1346 pattern = pattern.replaceFirst("M", "MM");
1347 sdf.applyPattern(pattern);
1348 }
1349 if (!pattern.contains("dd")) {
1350 // change the pattern to be a two digit day
1351 pattern = pattern.replaceFirst("d", "dd");
1352 sdf.applyPattern(pattern);
1353 }
1354
1355 dateFormatCache.put(locale, sdf);
1356
1357 return (DateFormat) sdf.clone();
1358 }
1359
1360 /**
1361 * Get the current user's time format Will look similar to "hh:mm a". Depends on user's locale.
1362 *
1363 * @return a simple time format
1364 * @should return a pattern with two h characters in it
1365 * @should not allow the returned SimpleDateFormat to be modified
1366 * @since 1.9
1367 */
1368 public static SimpleDateFormat getTimeFormat(Locale locale) {
1369 if (timeFormatCache.containsKey(locale)) {
1370 return (SimpleDateFormat) timeFormatCache.get(locale).clone();
1371 }
1372
1373 SimpleDateFormat sdf = (SimpleDateFormat) DateFormat.getTimeInstance(DateFormat.SHORT, locale);
1374 String pattern = sdf.toPattern();
1375
1376 if (!(pattern.contains("hh") || pattern.contains("HH"))) {
1377 // otherwise, change the pattern to be a two digit hour
1378 pattern = pattern.replaceFirst("h", "hh").replaceFirst("H", "HH");
1379 sdf.applyPattern(pattern);
1380 }
1381
1382 timeFormatCache.put(locale, sdf);
1383
1384 return (SimpleDateFormat) sdf.clone();
1385 }
1386
1387 /**
1388 * Get the current user's datetime format Will look similar to "mm-dd-yyyy hh:mm a". Depends on
1389 * user's locale.
1390 *
1391 * @return a simple date format
1392 * @should return a pattern with four y characters and two h characters in it
1393 * @should not allow the returned SimpleDateFormat to be modified
1394 * @since 1.9
1395 */
1396 public static SimpleDateFormat getDateTimeFormat(Locale locale) {
1397 SimpleDateFormat dateFormat;
1398 SimpleDateFormat timeFormat;
1399
1400 dateFormat = (SimpleDateFormat) getDateFormat(locale);
1401 timeFormat = getTimeFormat(locale);
1402
1403 String pattern = dateFormat.toPattern() + " " + timeFormat.toPattern();
1404 SimpleDateFormat sdf = new SimpleDateFormat();
1405 sdf.applyPattern(pattern);
1406 return sdf;
1407 }
1408
1409 /**
1410 * Takes a String (e.g. a user-entered one) and parses it into an object of the specified class
1411 *
1412 * @param string
1413 * @param clazz
1414 * @return Object of type <code>clazz</code> with the data from <code>string</code>
1415 */
1416 @SuppressWarnings("unchecked")
1417 public static Object parse(String string, Class clazz) {
1418 try {
1419 // If there's a valueOf(String) method, just use that (will cover at
1420 // least String, Integer, Double, Boolean)
1421 Method valueOfMethod = null;
1422 try {
1423 valueOfMethod = clazz.getMethod("valueOf", String.class);
1424 }
1425 catch (NoSuchMethodException ex) {}
1426 if (valueOfMethod != null) {
1427 return valueOfMethod.invoke(null, string);
1428 } else if (clazz.isEnum()) {
1429 // Special-case for enum types
1430 List<Enum> constants = Arrays.asList((Enum[]) clazz.getEnumConstants());
1431 for (Enum e : constants) {
1432 if (e.toString().equals(string)) {
1433 return e;
1434 }
1435 }
1436 throw new IllegalArgumentException(string + " is not a legal value of enum class " + clazz);
1437 } else if (String.class.equals(clazz)) {
1438 return string;
1439 } else if (Location.class.equals(clazz)) {
1440 try {
1441 Integer.parseInt(string);
1442 LocationEditor ed = new LocationEditor();
1443 ed.setAsText(string);
1444 return ed.getValue();
1445 }
1446 catch (NumberFormatException ex) {
1447 return Context.getLocationService().getLocation(string);
1448 }
1449 } else if (Concept.class.equals(clazz)) {
1450 ConceptEditor ed = new ConceptEditor();
1451 ed.setAsText(string);
1452 return ed.getValue();
1453 } else if (Program.class.equals(clazz)) {
1454 ProgramEditor ed = new ProgramEditor();
1455 ed.setAsText(string);
1456 return ed.getValue();
1457 } else if (ProgramWorkflowState.class.equals(clazz)) {
1458 ProgramWorkflowStateEditor ed = new ProgramWorkflowStateEditor();
1459 ed.setAsText(string);
1460 return ed.getValue();
1461 } else if (EncounterType.class.equals(clazz)) {
1462 EncounterTypeEditor ed = new EncounterTypeEditor();
1463 ed.setAsText(string);
1464 return ed.getValue();
1465 } else if (Form.class.equals(clazz)) {
1466 FormEditor ed = new FormEditor();
1467 ed.setAsText(string);
1468 return ed.getValue();
1469 } else if (Drug.class.equals(clazz)) {
1470 DrugEditor ed = new DrugEditor();
1471 ed.setAsText(string);
1472 return ed.getValue();
1473 } else if (PersonAttributeType.class.equals(clazz)) {
1474 PersonAttributeTypeEditor ed = new PersonAttributeTypeEditor();
1475 ed.setAsText(string);
1476 return ed.getValue();
1477 } else if (Cohort.class.equals(clazz)) {
1478 CohortEditor ed = new CohortEditor();
1479 ed.setAsText(string);
1480 return ed.getValue();
1481 } else if (Date.class.equals(clazz)) {
1482 // TODO: this uses the date format from the current session,
1483 // which could cause problems if the user changes it after
1484 // searching.
1485 CustomDateEditor ed = new CustomDateEditor(Context.getDateFormat(), true, 10);
1486 ed.setAsText(string);
1487 return ed.getValue();
1488 } else if (Object.class.equals(clazz)) {
1489 // TODO: Decide whether this is a hack. Currently setting Object
1490 // arguments with a String
1491 return string;
1492 } else {
1493 throw new IllegalArgumentException("Don't know how to handle class: " + clazz);
1494 }
1495 }
1496 catch (Exception ex) {
1497 log.error("error converting \"" + string + "\" to " + clazz, ex);
1498 throw new IllegalArgumentException(ex);
1499 }
1500 }
1501
1502 /**
1503 * Loops over the collection to check to see if the given object is in that collection. This
1504 * method <i>only</i> uses the .equals() method for comparison. This should be used in the
1505 * patient/person objects on their collections. Their collections are SortedSets which use the
1506 * compareTo method for equality as well. The compareTo method is currently optimized for
1507 * sorting, not for equality. A null <code>obj</code> will return false
1508 *
1509 * @param objects collection to loop over
1510 * @param obj Object to look for in the <code>objects</code>
1511 * @return true/false whether the given object is found
1512 * @should use equals method for comparison instead of compareTo given List collection
1513 * @should use equals method for comparison instead of compareTo given SortedSet collection
1514 */
1515 public static boolean collectionContains(Collection<?> objects, Object obj) {
1516 if (obj == null || objects == null) {
1517 return false;
1518 }
1519
1520 for (Object o : objects) {
1521 if (o != null && o.equals(obj)) {
1522 return true;
1523 }
1524 }
1525
1526 return false;
1527 }
1528
1529 /**
1530 * Gets an out File object. If date is not provided, the current timestamp is used. If user is
1531 * not provided, the user id is not put into the filename. Assumes dir is already created
1532 *
1533 * @param dir directory to make the random filename in
1534 * @param date optional Date object used for the name
1535 * @param user optional User creating this file object
1536 * @return file new file that is able to be written to
1537 */
1538 public static File getOutFile(File dir, Date date, User user) {
1539 Random gen = new Random();
1540 File outFile;
1541 do {
1542 // format to print date in filename
1543 DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd-HHmm-ssSSS");
1544
1545 // use current date if none provided
1546 if (date == null) {
1547 date = new Date();
1548 }
1549
1550 StringBuilder filename = new StringBuilder();
1551
1552 // the start of the filename is the time so we can do some sorting
1553 filename.append(dateFormat.format(date));
1554
1555 // insert the user id if they provided it
1556 if (user != null) {
1557 filename.append("-");
1558 filename.append(user.getUserId());
1559 filename.append("-");
1560 }
1561
1562 // the end of the filename is a randome number between 0 and 10000
1563 filename.append(gen.nextInt() * 10000);
1564 filename.append(".xml");
1565
1566 outFile = new File(dir, filename.toString());
1567
1568 // set to null to avoid very minimal possiblity of an infinite loop
1569 date = null;
1570
1571 } while (outFile.exists());
1572
1573 return outFile;
1574 }
1575
1576 /**
1577 * Creates a relatively acceptable unique string of the give size
1578 *
1579 * @return unique string
1580 */
1581 public static String generateUid(Integer size) {
1582 Random gen = new Random();
1583 StringBuilder sb = new StringBuilder(size);
1584 for (int i = 0; i < size; i++) {
1585 int ch = gen.nextInt() * 62;
1586 if (ch < 10) {
1587 // 0-9
1588 sb.append(ch);
1589 } else if (ch < 36) {
1590 // a-z
1591 sb.append((char) (ch - 10 + 'a'));
1592 } else {
1593 sb.append((char) (ch - 36 + 'A'));
1594 }
1595 }
1596 return sb.toString();
1597 }
1598
1599 /**
1600 * Creates a uid of length 20
1601 *
1602 * @see #generateUid(Integer)
1603 */
1604 public static String generateUid() {
1605 return generateUid(20);
1606 }
1607
1608 /**
1609 * Convenience method to replace Properties.store(), which isn't UTF-8 compliant <br>
1610 * NOTE: In Java 6, you will be able to pass the load() and store() methods a UTF-8
1611 * Reader/Writer object as an argument, making this method unnecessary.
1612 *
1613 * @param properties
1614 * @param file
1615 * @param comment
1616 */
1617 public static void storeProperties(Properties properties, File file, String comment) {
1618 OutputStream outStream = null;
1619 try {
1620 outStream = new FileOutputStream(file, true);
1621 storeProperties(properties, outStream, comment);
1622 }
1623 catch (IOException ex) {
1624 log.error("Unable to create file " + file.getAbsolutePath() + " in storeProperties routine.");
1625 }
1626 finally {
1627 try {
1628 if (outStream != null) {
1629 outStream.close();
1630 }
1631 }
1632 catch (IOException ioe) {
1633 // pass
1634 }
1635 }
1636 }
1637
1638 /**
1639 * Convenience method to replace Properties.store(), which isn't UTF-8 compliant NOTE: In Java
1640 * 6, you will be able to pass the load() and store() methods a UTF-8 Reader/Writer object as an
1641 * argument.
1642 *
1643 * @param properties
1644 * @param outStream
1645 * @param comment (which appears in comments in properties file)
1646 */
1647 public static void storeProperties(Properties properties, OutputStream outStream, String comment) {
1648 try {
1649 Charset utf8 = StandardCharsets.UTF_8;
1650 properties.store(new OutputStreamWriter(outStream, utf8), comment);
1651 }
1652 catch (FileNotFoundException fnfe) {
1653 log.error("target file not found" + fnfe);
1654 }
1655 catch (UnsupportedEncodingException ex) { // pass
1656 log.error("unsupported encoding error hit" + ex);
1657 }
1658 catch (IOException ioex) {
1659 log.error("IO exception encountered trying to append to properties file" + ioex);
1660 }
1661
1662 }
1663
1664 /**
1665 * This method is a replacement for Properties.load(InputStream) so that we can load in utf-8
1666 * characters. Currently the load method expects the inputStream to point to a latin1 encoded
1667 * file. <br>
1668 * NOTE: In Java 6, you will be able to pass the load() and store() methods a UTF-8
1669 * Reader/Writer object as an argument, making this method unnecessary.
1670 *
1671 * @param props the properties object to write into
1672 * @param input the input stream to read from
1673 */
1674 public static void loadProperties(Properties props, InputStream inputStream) {
1675 InputStreamReader reader = null;
1676 try {
1677 reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
1678 props.load(reader);
1679 }
1680 catch (FileNotFoundException fnfe) {
1681 log.error("Unable to find properties file" + fnfe);
1682 }
1683 catch (UnsupportedEncodingException uee) {
1684 log.error("Unsupported encoding used in properties file" + uee);
1685 }
1686 catch (IOException ioe) {
1687 log.error("Unable to read properties from properties file" + ioe);
1688 }
1689 finally {
1690 try {
1691 if (reader != null) {
1692 reader.close();
1693 }
1694 }
1695 catch (IOException ioe) {
1696 log.error("Unable to close properties file " + ioe);
1697 }
1698 }
1699 }
1700
1701 /**
1702 * Convenience method used to load properties from the given file.
1703 *
1704 * @param props the properties object to be loaded into
1705 * @param propertyFile the properties file to read
1706 */
1707 public static void loadProperties(Properties props, File propertyFile) {
1708 try {
1709 loadProperties(props, new FileInputStream(propertyFile));
1710 }
1711 catch (FileNotFoundException fnfe) {
1712 log.error("Unable to find properties file" + fnfe);
1713 }
1714 }
1715
1716 /**
1717 * Utility method for getting the translation for the passed code
1718 *
1719 * @param code the message key to lookup
1720 * @param args the replacement values for the translation string
1721 * @return the message, or if not found, the code
1722 */
1723 public static String getMessage(String code, Object... args) {
1724 Locale l = Context.getLocale();
1725 try {
1726 String translation = Context.getMessageSourceService().getMessage(code, args, l);
1727 if (translation != null) {
1728 return translation;
1729 }
1730 }
1731 catch (NoSuchMessageException e) {
1732 log.warn("Message code <" + code + "> not found for locale " + l);
1733 }
1734 catch (APIException apiEx) {
1735 // in case the services aren't set up yet
1736 log.debug("Unable to get code: " + code, apiEx);
1737 return code;
1738 }
1739 return code;
1740 }
1741
1742 /**
1743 * Utility to check the validity of a password for a certain {@link User}. Passwords must be
1744 * non-null. Their required strength is configured via global properties:
1745 * <table summary="Configuration props">
1746 * <tr>
1747 * <th>Description</th>
1748 * <th>Property</th>
1749 * <th>Default Value</th>
1750 * </tr>
1751 * <tr>
1752 * <th>Require that it not match the {@link User}'s username or system id
1753 * <th>{@link OpenmrsConstants#GP_PASSWORD_CANNOT_MATCH_USERNAME_OR_SYSTEMID}</th>
1754 * <th>true</th>
1755 * </tr>
1756 * <tr>
1757 * <th>Require a minimum length
1758 * <th>{@link OpenmrsConstants#GP_PASSWORD_MINIMUM_LENGTH}</th>
1759 * <th>8</th>
1760 * </tr>
1761 * <tr>
1762 * <th>Require both an upper and lower case character
1763 * <th>{@link OpenmrsConstants#GP_PASSWORD_REQUIRES_UPPER_AND_LOWER_CASE}</th>
1764 * <th>true</th>
1765 * </tr>
1766 * <tr>
1767 * <th>Require at least one numeric character
1768 * <th>{@link OpenmrsConstants#GP_PASSWORD_REQUIRES_DIGIT}</th>
1769 * <th>true</th>
1770 * </tr>
1771 * <tr>
1772 * <th>Require at least one non-numeric character
1773 * <th>{@link OpenmrsConstants#GP_PASSWORD_REQUIRES_NON_DIGIT}</th>
1774 * <th>true</th>
1775 * </tr>
1776 * <tr>
1777 * <th>Require a match on the specified regular expression
1778 * <th>{@link OpenmrsConstants#GP_PASSWORD_CUSTOM_REGEX}</th>
1779 * <th>null</th>
1780 * </tr>
1781 * </table>
1782 *
1783 * @param username user name of the user with password to validated
1784 * @param password string that will be validated
1785 * @param systemId system id of the user with password to be validated
1786 * @throws PasswordException
1787 * @since 1.5
1788 * @should fail with short password by default
1789 * @should fail with short password if not allowed
1790 * @should pass with short password if allowed
1791 * @should fail with digit only password by default
1792 * @should fail with digit only password if not allowed
1793 * @should pass with digit only password if allowed
1794 * @should fail with char only password by default
1795 * @should fail with char only password if not allowed
1796 * @should pass with char only password if allowed
1797 * @should fail without both upper and lower case password by default
1798 * @should fail without both upper and lower case password if not allowed
1799 * @should pass without both upper and lower case password if allowed
1800 * @should fail with password equals to user name by default
1801 * @should fail with password equals to user name if not allowed
1802 * @should pass with password equals to user name if allowed
1803 * @should fail with password equals to system id by default
1804 * @should fail with password equals to system id if not allowed
1805 * @should pass with password equals to system id if allowed
1806 * @should fail with password not matching configured regex
1807 * @should pass with password matching configured regex
1808 * @should allow password to contain non alphanumeric characters
1809 * @should allow password to contain white spaces
1810 * @should still work without an open session
1811 */
1812 public static void validatePassword(String username, String password, String systemId) throws PasswordException {
1813
1814 // default values for all of the global properties
1815 String userGp = "true";
1816 String lengthGp = "8";
1817 String caseGp = "true";
1818 String digitGp = "true";
1819 String nonDigitGp = "true";
1820 String regexGp = null;
1821 AdministrationService svc = null;
1822
1823 try {
1824 svc = Context.getAdministrationService();
1825 }
1826 catch (APIException apiEx) {
1827 // if a service isn't available, fail quietly and just do the
1828 // defaults
1829 log.debug("Unable to get global properties", apiEx);
1830 }
1831
1832 if (svc != null && Context.isSessionOpen()) {
1833 // (the session won't be open here to allow for the unit test to
1834 // fake not having the admin service available)
1835 userGp = svc.getGlobalProperty(OpenmrsConstants.GP_PASSWORD_CANNOT_MATCH_USERNAME_OR_SYSTEMID, userGp);
1836 lengthGp = svc.getGlobalProperty(OpenmrsConstants.GP_PASSWORD_MINIMUM_LENGTH, lengthGp);
1837 caseGp = svc.getGlobalProperty(OpenmrsConstants.GP_PASSWORD_REQUIRES_UPPER_AND_LOWER_CASE, caseGp);
1838 digitGp = svc.getGlobalProperty(OpenmrsConstants.GP_PASSWORD_REQUIRES_DIGIT, digitGp);
1839 nonDigitGp = svc.getGlobalProperty(OpenmrsConstants.GP_PASSWORD_REQUIRES_NON_DIGIT, nonDigitGp);
1840 regexGp = svc.getGlobalProperty(OpenmrsConstants.GP_PASSWORD_CUSTOM_REGEX, regexGp);
1841 }
1842
1843 if (password == null) {
1844 throw new WeakPasswordException();
1845 }
1846
1847 if ("true".equals(userGp) && (password.equals(username) || password.equals(systemId))) {
1848 throw new WeakPasswordException();
1849 }
1850
1851 if (StringUtils.isNotEmpty(lengthGp)) {
1852 try {
1853 int minLength = Integer.parseInt(lengthGp);
1854 if (password.length() < minLength) {
1855 throw new ShortPasswordException(getMessage("error.password.length", lengthGp));
1856 }
1857 }
1858 catch (NumberFormatException nfe) {
1859 log.warn(
1860 "Error in global property <" + OpenmrsConstants.GP_PASSWORD_MINIMUM_LENGTH + "> must be an Integer");
1861 }
1862 }
1863
1864 if ("true".equals(caseGp) && !containsUpperAndLowerCase(password)) {
1865 throw new InvalidCharactersPasswordException(getMessage("error.password.requireMixedCase"));
1866 }
1867
1868 if ("true".equals(digitGp) && !containsDigit(password)) {
1869 throw new InvalidCharactersPasswordException(getMessage("error.password.requireNumber"));
1870 }
1871
1872 if ("true".equals(nonDigitGp) && containsOnlyDigits(password)) {
1873 throw new InvalidCharactersPasswordException(getMessage("error.password.requireLetter"));
1874 }
1875
1876 if (StringUtils.isNotEmpty(regexGp)) {
1877 try {
1878 Pattern pattern = Pattern.compile(regexGp);
1879 Matcher matcher = pattern.matcher(password);
1880 if (!matcher.matches()) {
1881 throw new InvalidCharactersPasswordException(getMessage("error.password.different"));
1882 }
1883 }
1884 catch (PatternSyntaxException pse) {
1885 log.warn("Invalid regex of " + regexGp + " defined in global property <"
1886 + OpenmrsConstants.GP_PASSWORD_CUSTOM_REGEX + ">.");
1887 }
1888 }
1889 }
1890
1891 /**
1892 * @param test the string to test
1893 * @return true if the passed string contains both upper and lower case characters
1894 * @should return true if string contains upper and lower case
1895 * @should return false if string does not contain lower case characters
1896 * @should return false if string does not contain upper case characters
1897 */
1898 public static boolean containsUpperAndLowerCase(String test) {
1899 if (test != null) {
1900 Pattern pattern = Pattern.compile("^(?=.*?[A-Z])(?=.*?[a-z])[\\w|\\W]*$");
1901 Matcher matcher = pattern.matcher(test);
1902 return matcher.matches();
1903 }
1904 return false;
1905 }
1906
1907 /**
1908 * @param test the string to test
1909 * @return true if the passed string contains only numeric characters
1910 * @should return true if string contains only digits
1911 * @should return false if string contains any non-digits
1912 */
1913 public static boolean containsOnlyDigits(String test) {
1914 if (test != null) {
1915 for (char c : test.toCharArray()) {
1916 if (!Character.isDigit(c)) {
1917 return false;
1918 }
1919 }
1920 }
1921 return StringUtils.isNotEmpty(test);
1922 }
1923
1924 /**
1925 * @param test the string to test
1926 * @return true if the passed string contains any numeric characters
1927 * @should return true if string contains any digits
1928 * @should return false if string contains no digits
1929 */
1930 public static boolean containsDigit(String test) {
1931 if (test != null) {
1932 for (char c : test.toCharArray()) {
1933 if (Character.isDigit(c)) {
1934 return true;
1935 }
1936 }
1937 }
1938 return false;
1939 }
1940
1941 /**
1942 * A null-safe and exception safe way to close an inputstream or an outputstream
1943 *
1944 * @param closableStream an InputStream or OutputStream to close
1945 */
1946 public static void closeStream(Closeable closableStream) {
1947 if (closableStream != null) {
1948 try {
1949 closableStream.close();
1950 }
1951 catch (IOException io) {
1952 log.trace("Error occurred while closing stream", io);
1953 }
1954 }
1955 }
1956
1957 /**
1958 * Convert a stack trace into a shortened version for easier viewing and data storage, excluding
1959 * those lines we are least concerned with; should average about 60% reduction in stack trace
1960 * length
1961 *
1962 * @param stackTrace original stack trace from an error
1963 * @return shortened stack trace
1964 * @should return null if stackTrace is null
1965 * @should remove springframework and reflection related lines
1966 * @since 1.7
1967 */
1968 public static String shortenedStackTrace(String stackTrace) {
1969 if (stackTrace == null) {
1970 return null;
1971 }
1972
1973 List<String> results = new ArrayList<>();
1974 final Pattern exclude = Pattern.compile("(org.springframework.|java.lang.reflect.Method.invoke|sun.reflect.)");
1975 boolean found = false;
1976
1977 for (String line : stackTrace.split("\n")) {
1978 Matcher m = exclude.matcher(line);
1979 if (m.find()) {
1980 found = true;
1981 } else {
1982 if (found) {
1983 found = false;
1984 results.add("\tat [ignored] ...");
1985 }
1986 results.add(line);
1987 }
1988 }
1989
1990 return StringUtils.join(results, "\n");
1991 }
1992
1993 /**
1994 * <pre>
1995 * Finds and loads the runtime properties file for a specific OpenMRS application.
1996 * Searches for the file in this order:
1997 * 1) {current directory}/{applicationname}_runtime.properties
1998 * 2) an environment variable called "{APPLICATIONNAME}_RUNTIME_PROPERTIES_FILE"
1999 * 3) {openmrs_app_dir}/{applicationName}_runtime.properties // openmrs_app_dir is typically {user_home}/.OpenMRS
2000 * </pre>
2001 *
2002 * @see #getApplicationDataDirectory()
2003 * @param applicationName (defaults to "openmrs") the name of the running OpenMRS application,
2004 * e.g. if you have deployed OpenMRS as a web application you would give the deployed
2005 * context path here
2006 * @return runtime properties, or null if none can be found
2007 * @since 1.8
2008 */
2009 public static Properties getRuntimeProperties(String applicationName) {
2010 if (applicationName == null) {
2011 applicationName = "openmrs";
2012 }
2013 String pathName;
2014 pathName = getRuntimePropertiesFilePathName(applicationName);
2015 FileInputStream propertyStream = null;
2016 try {
2017 if (pathName != null) {
2018 propertyStream = new FileInputStream(pathName);
2019 }
2020 }
2021 catch (FileNotFoundException e) {
2022 log.warn("Unable to find a runtime properties file at " + new File(pathName).getAbsolutePath());
2023 }
2024
2025 try {
2026 if (propertyStream == null) {
2027 throw new IOException("Could not find a runtime properties file named " + pathName
2028 + " in the OpenMRS application data directory, or the current directory");
2029 }
2030
2031 Properties props = new Properties();
2032 OpenmrsUtil.loadProperties(props, propertyStream);
2033 propertyStream.close();
2034 log.info("Using runtime properties file: " + pathName);
2035 return props;
2036 }
2037 catch (Exception ex) {
2038 log.info("Got an error while attempting to load the runtime properties", ex);
2039 log.warn(
2040 "Unable to find a runtime properties file. Initial setup is needed. View the webapp to run the setup wizard.");
2041 return null;
2042 }
2043 }
2044
2045 /**
2046 * Checks whether the system is running in test mode
2047 *
2048 * @return boolean
2049 */
2050
2051 public static boolean isTestMode() {
2052 return "true".equalsIgnoreCase(System.getProperty("FUNCTIONAL_TEST_MODE"));
2053 }
2054
2055 /**
2056 * Gets the full path and name of the runtime properties file.
2057 *
2058 * @param applicationName (defaults to "openmrs") the name of the running OpenMRS application,
2059 * e.g. if you have deployed OpenMRS as a web application you would give the deployed
2060 * context path here
2061 * @return runtime properties file path and name, or null if none can be found
2062 * @since 1.9
2063 */
2064 public static String getRuntimePropertiesFilePathName(String applicationName) {
2065 if (applicationName == null) {
2066 applicationName = "openmrs";
2067 }
2068
2069 String defaultFileName = applicationName + "-runtime.properties";
2070 String fileNameInTestMode = getRuntimePropertiesFileNameInTestMode();
2071
2072 // first look in the current directory (that java was started from)
2073 String pathName = fileNameInTestMode != null ? fileNameInTestMode : defaultFileName;
2074 log.debug("Attempting to look for properties file in current directory: " + pathName);
2075 if (new File(pathName).exists()) {
2076 return pathName;
2077 } else {
2078 log.warn("Unable to find a runtime properties file at " + new File(pathName).getAbsolutePath());
2079 }
2080
2081 // next look from environment variable
2082 String envVarName = applicationName.toUpperCase() + "_RUNTIME_PROPERTIES_FILE";
2083 String envFileName = System.getenv(envVarName);
2084 if (envFileName != null) {
2085 log.debug("Atempting to look for runtime properties from: " + pathName);
2086 if (new File(envFileName).exists()) {
2087 return envFileName;
2088 } else {
2089 log.warn("Unable to find properties file with path: " + pathName + ". (derived from environment variable "
2090 + envVarName + ")");
2091 }
2092 } else {
2093 log.info("Couldn't find an environment variable named " + envVarName);
2094 if (log.isDebugEnabled()) {
2095 log.debug("Available environment variables are named: " + System.getenv().keySet());
2096 }
2097 }
2098
2099 // next look in the OpenMRS application data directory
2100 File file = new File(getApplicationDataDirectory(), pathName);
2101 pathName = file.getAbsolutePath();
2102 log.debug("Attempting to look for property file from: " + pathName);
2103 if (file.exists()) {
2104 return pathName;
2105 } else {
2106 log.warn("Unable to find properties file: " + pathName);
2107 }
2108
2109 return null;
2110 }
2111
2112 public static String getRuntimePropertiesFileNameInTestMode() {
2113 String filename = null;
2114 if (isTestMode()) {
2115 log.info("In functional testing mode. Ignoring the existing runtime properties file");
2116 filename = getOpenMRSVersionInTestMode() + "-test-runtime.properties";
2117 }
2118 return filename;
2119 }
2120
2121 /**
2122 * Gets OpenMRS version name under test mode.
2123 *
2124 * @return String openmrs version number
2125 */
2126 public static String getOpenMRSVersionInTestMode() {
2127 return System.getProperty("OPENMRS_VERSION", "openmrs");
2128 }
2129
2130 /**
2131 * Performs a case insensitive Comparison of two strings taking care of null values
2132 *
2133 * @param s1 the string to compare
2134 * @param s2 the string to compare
2135 * @return true if strings are equal (ignoring case)
2136 * @should return false if only one of the strings is null
2137 * @should be case insensitive
2138 * @since 1.8
2139 */
2140 public static boolean nullSafeEqualsIgnoreCase(String s1, String s2) {
2141 if (s1 == null) {
2142 return s2 == null;
2143 } else if (s2 == null) {
2144 return false;
2145 }
2146
2147 return s1.equalsIgnoreCase(s2);
2148 }
2149
2150 /**
2151 * This method converts the given Long value to an Integer. If the Long value will not fit in an
2152 * Integer an exception is thrown
2153 *
2154 * @param longValue the value to convert
2155 * @return the long value in integer form.
2156 * @throws IllegalArgumentException if the long value does not fit into an integer
2157 */
2158 public static Integer convertToInteger(Long longValue) {
2159 if (longValue < Integer.MIN_VALUE || longValue > Integer.MAX_VALUE) {
2160 throw new IllegalArgumentException(longValue + " cannot be cast to Integer without changing its value.");
2161 }
2162 return longValue.intValue();
2163 }
2164
2165 /**
2166 * Checks if the passed in date's day of the year is the one that comes immediately before that
2167 * of the current date
2168 *
2169 * @param date the date to check
2170 * @since 1.9
2171 * @return true if the date comes immediately before the current date otherwise false
2172 */
2173 public static boolean isYesterday(Date date) {
2174 if (date == null) {
2175 return false;
2176 }
2177
2178 Calendar c1 = Calendar.getInstance();
2179 c1.add(Calendar.DAY_OF_YEAR, -1); // yesterday
2180
2181 Calendar c2 = Calendar.getInstance();
2182 c2.setTime(date);
2183
2184 return (c1.get(Calendar.ERA) == c2.get(Calendar.ERA) && c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR)
2185 && c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR));
2186 }
2187
2188 /**
2189 * Get declared field names of a class
2190 *
2191 * @param clazz
2192 * @return
2193 */
2194 public static Set<String> getDeclaredFields(Class<?> clazz) {
2195 return Arrays.stream(clazz.getDeclaredFields()).map(Field::getName).collect(Collectors.toSet());
2196 }
2197
2198}