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 .emrapi .utils ;
15+
16+ import org .apache .commons .beanutils .PropertyUtils ;
17+ import org .apache .commons .io .IOUtils ;
18+ import org .apache .commons .logging .Log ;
19+ import org .apache .commons .logging .LogFactory ;
20+ import org .openmrs .Auditable ;
21+ import org .openmrs .Concept ;
22+ import org .openmrs .OpenmrsMetadata ;
23+ import org .openmrs .api .context .Context ;
24+ import org .openmrs .module .emrapi .metadata .MetadataPackageConfig ;
25+ import org .openmrs .module .emrapi .metadata .MetadataPackagesConfig ;
26+ import org .openmrs .module .metadatasharing .ImportConfig ;
27+ import org .openmrs .module .metadatasharing .ImportedItem ;
28+ import org .openmrs .module .metadatasharing .ImportedPackage ;
29+ import org .openmrs .module .metadatasharing .MetadataSharing ;
30+ import org .openmrs .module .metadatasharing .api .MetadataSharingService ;
31+ import org .openmrs .module .metadatasharing .wrapper .PackageImporter ;
32+
33+ import java .io .IOException ;
34+ import java .io .InputStream ;
35+ import java .util .ArrayList ;
36+ import java .util .Arrays ;
37+ import java .util .Collection ;
38+ import java .util .Collections ;
39+ import java .util .Comparator ;
40+ import java .util .Date ;
41+ import java .util .HashMap ;
42+ import java .util .Iterator ;
43+ import java .util .List ;
44+ import java .util .Map ;
45+ import java .util .Set ;
46+ import java .util .TreeMap ;
47+ import java .util .TreeSet ;
48+ import java .util .regex .Matcher ;
49+ import java .util .regex .Pattern ;
50+
51+ /*@deprecated As of 1.31.0.This class has been moved to the openmrs Metadata Sharing Module
52+ * Ensure you have at least version 1.6.0 of Metadata Sharing Module
53+ * Change this to call class org.openmrs.module.metadatasharing.packages.MetadataUtil.java
54+ * Change your metadata files to use aliased name <MetadataPackagesConfig>
55+ * rather than the full name <org.openmrs.module.emrapi.metadata.MetadataPackagesConfig>
56+ * Change your metadata files to use aliased name <MetadataPackageConfig>
57+ * rather than the full name <org.openmrs.module.emrapi.metadata.MetadataPackageConfig>
58+ * See more info at {@link https://issues.openmrs.org/browse/EA-91}
59+ * */
60+ @ Deprecated
61+ public class MetadataUtil {
62+
63+ protected static final Log log = LogFactory .getLog (MetadataUtil .class );
64+
65+ public static final String PACKAGES_FILENAME = "packages.xml" ;
66+
67+ /**
68+ * Setup the standard metadata packages
69+ *
70+ * @return
71+ */
72+ public static boolean setupStandardMetadata (ClassLoader loader ) throws Exception {
73+ return setupStandardMetadata (loader , PACKAGES_FILENAME );
74+ }
75+
76+ public static boolean setupStandardMetadata (ClassLoader loader , String packagesFilePath ) throws Exception {
77+ MetadataPackagesConfig config = getMetadataPackagesForModule (loader , packagesFilePath );
78+ return loadPackages (config , loader );
79+ }
80+
81+ /**
82+ * Useful for testing, e.g. if you need to load up a specific MDS package
83+ * @param loader
84+ * @param namesToLoad something like Reference_Application_Visit_and_Encounter_Types
85+ * @return
86+ * @throws Exception
87+ */
88+ public static boolean setupSpecificMetadata (ClassLoader loader , String ... namesToLoad ) throws Exception {
89+ List <String > namesToKeep = Arrays .asList (namesToLoad );
90+ MetadataPackagesConfig config = getMetadataPackagesForModule (loader );
91+ for (Iterator <MetadataPackageConfig > i = config .getPackages ().iterator (); i .hasNext (); ) {
92+ MetadataPackageConfig packageConfig = i .next ();
93+ if (!namesToKeep .contains (packageConfig .getFilenameBase ())) {
94+ i .remove ();
95+ }
96+ }
97+ return loadPackages (config , loader );
98+ }
99+
100+ private synchronized static boolean loadPackages (MetadataPackagesConfig config , ClassLoader loader ) throws IOException {
101+
102+ List <PackageImporter > packageImporters = new ArrayList <PackageImporter >();
103+
104+ // first, instantiate and load a package importer for each package
105+ for (MetadataPackageConfig pkg : config .getPackages ()) {
106+ PackageImporter packageImporter = loadPackageImporterIfNecessary (pkg , loader );
107+ if (packageImporter != null ) {
108+ packageImporters .add (packageImporter );
109+ }
110+ }
111+
112+ // do the actual imports
113+ if (packageImporters .size () > 0 ) {
114+
115+ // sort in order of date created, with most recently created coming last: this is so in the case of inconsistent metadata between packages, the most recent one wins
116+ Collections .sort (packageImporters , new Comparator <PackageImporter >() {
117+ @ Override
118+ public int compare (PackageImporter i1 , PackageImporter i2 ) {
119+ return i1 .getImportedPackage ().getDateCreated ().compareTo (i2 .getImportedPackage ().getDateCreated ());
120+ }
121+ });
122+
123+ for (PackageImporter packageImporter : packageImporters ) {
124+ long timer = System .currentTimeMillis ();
125+ log .info ("Importing package: " + packageImporter .getImportedPackage ().getName ());
126+ packageImporter .importPackage ();
127+ log .info ("Imported " + packageImporter .getImportedPackage ().getName () + " in " + (System .currentTimeMillis () - timer ) + "ms" );
128+ }
129+ return true ;
130+ }
131+
132+ return false ;
133+ }
134+
135+ public static MetadataPackagesConfig getMetadataPackagesForModule (ClassLoader loader ) {
136+ return getMetadataPackagesForModule (loader , PACKAGES_FILENAME );
137+ }
138+
139+ public static MetadataPackagesConfig getMetadataPackagesForModule (ClassLoader loader , String packageFilePath ) {
140+ try {
141+ InputStream stream = loader .getResourceAsStream (packageFilePath );
142+ String xml = IOUtils .toString (stream );
143+ MetadataPackagesConfig config = Context .getSerializationService ().getDefaultSerializer ()
144+ .deserialize (xml , MetadataPackagesConfig .class );
145+ return config ;
146+ }
147+ catch (Exception ex ) {
148+ throw new RuntimeException ("Cannot find " + packageFilePath + ", or error deserializing it" , ex );
149+ }
150+ }
151+
152+ /**
153+ * Checks whether the given version of the MDS package has been installed yet, and if not,
154+ * install it
155+ *
156+ * @param config the metadata package configuration object
157+ * @param loader the class loader to use for loading the packages
158+ * @return whether any changes were made to the db
159+ * @throws IOException
160+ */
161+ private static PackageImporter loadPackageImporterIfNecessary (MetadataPackageConfig config , ClassLoader loader )
162+ throws IOException {
163+ String filename = config .getFilenameBase () + "-" + config .getVersion ().toString () + ".zip" ;
164+ try {
165+
166+ Matcher matcher = Pattern .compile ("(?:.+/)?\\ w+-(\\ d+).zip" ).matcher (filename );
167+ if (!matcher .matches ())
168+ throw new RuntimeException ("Filename must match PackageNameWithNoSpaces-X.zip" );
169+ Integer version = Integer .valueOf (matcher .group (1 ));
170+
171+ ImportedPackage installed = Context .getService (MetadataSharingService .class ).getImportedPackageByGroup (
172+ config .getGroupUuid ());
173+ if (installed != null && installed .getVersion () >= version ) {
174+ log .info ("Metadata package " + config .getFilenameBase () + " is already installed with version "
175+ + installed .getVersion ());
176+ return null ;
177+ }
178+
179+ if (loader .getResource (filename ) == null ) {
180+ throw new RuntimeException ("Cannot find " + filename + " for group " + config .getGroupUuid ());
181+ }
182+
183+ PackageImporter metadataImporter = MetadataSharing .getInstance ().newPackageImporter ();
184+ metadataImporter .setImportConfig (ImportConfig .valueOf (config .getImportMode ()));
185+ log .info ("...loading package: " + filename );
186+ metadataImporter .loadSerializedPackageStream (loader .getResourceAsStream (filename ));
187+ return metadataImporter ;
188+ }
189+ catch (Exception ex ) {
190+ log .error ("Failed to install metadata package " + filename , ex );
191+ return null ;
192+ }
193+ }
194+
195+ /**
196+ * If multiple MDS packages contain different versions of the same item, then loading them is order-dependent, which
197+ * is bad.
198+ * Any OpenMRS distribution that uses the #installMetadataPackageIfNecessary functionality in this class should
199+ * have a one-line unit test that calls this method, basically like:
200+ * <pre>
201+ * public class InconsistantMetadataTest extends BaseModuleContextSensitiveTest {
202+ * @Test
203+ * public void testThatThereAreNoMdsPackagesWithInconsistentVersionsOfTheSameItem() throws Exception {
204+ * MetadataUtil.verifyNoMdsPackagesWithInconsistentVersionsOfTheSameItem(getClass().getClassLoader());
205+ * }
206+ * }
207+ * </pre>
208+ *
209+ * @param classLoader
210+ * @throws Exception
211+ * @throws IllegalStateException if different versions of the same metadata item are contained in two packages
212+ */
213+ public static void verifyNoMdsPackagesWithInconsistentVersionsOfTheSameItem (ClassLoader classLoader ) throws Exception , IllegalStateException {
214+ ItemToDateMap itemToDateMap = new ItemToDateMap ();
215+ boolean metadataConsistent = true ;
216+
217+ MetadataPackagesConfig allConfigs = MetadataUtil .getMetadataPackagesForModule (classLoader );
218+ for (MetadataPackageConfig config : allConfigs .getPackages ()) {
219+ String filenameBase = config .getFilenameBase ();
220+ String filename = filenameBase + "-" + config .getVersion ().toString () + ".zip" ;
221+ log .debug ("Inspecting " + filename );
222+ System .out .println ("Inspecting " + filename );
223+
224+ PackageImporter metadataImporter = MetadataSharing .getInstance ().newPackageImporter ();
225+ metadataImporter .setImportConfig (ImportConfig .valueOf (config .getImportMode ()));
226+ metadataImporter .loadSerializedPackageStream (classLoader .getResourceAsStream (filename ));
227+ for (int i = 0 ; i < metadataImporter .getPartsCount (); ++i ) {
228+ Collection <ImportedItem > items = metadataImporter .getImportedItems (i );
229+ for (ImportedItem item : items ) {
230+ metadataConsistent = itemToDateMap .addItem (item , filenameBase ) && metadataConsistent ;
231+ for (ImportedItem related : item .getRelatedItems ()) {
232+ metadataConsistent = itemToDateMap .addItem (related , filenameBase ) && metadataConsistent ;
233+ }
234+ }
235+ }
236+
237+ log .debug ("Finished. Running total number of distinct items: " + itemToDateMap .size ());
238+ System .out .println ("Finished. Running total number of distinct items: " + itemToDateMap .size ());
239+
240+ Context .flushSession ();
241+ Context .clearSession ();
242+ }
243+
244+ if (log .isInfoEnabled ()) {
245+ Map <String , Set <String >> repeated = itemToDateMap .repeatedItems ();
246+ if (log .isDebugEnabled ()) {
247+ log .debug ("Items that occur in multiple MDS packages:" );
248+ for (Map .Entry <String , Set <String >> e : repeated .entrySet ()) {
249+ log .debug (e .getKey () + " -> " + e .getValue ());
250+ }
251+ }
252+ log .info ("Number of distinct items in multiple packages: " + repeated .size ());
253+ log .info ("Total number of distinct items: " + itemToDateMap .size ());
254+ }
255+
256+ if (!metadataConsistent ) {
257+ throw new IllegalStateException ("Found inconsistent metadata" );
258+ }
259+
260+ }
261+
262+ static class ItemToDateMap {
263+
264+ // classname + uuid to last date modified
265+ Map <String , Date > lastModifiedMap = new HashMap <String , Date >();
266+ Map <String , Set <String >> itemToPackages = new HashMap <String , Set <String >>();
267+
268+ public boolean addItem (ImportedItem item , String filename ) {
269+
270+ String key = getKey (item );
271+ Date lastModified = getLastModified (item .getIncoming ());
272+
273+ Set <String > belongsToPackages = itemToPackages .get (key );
274+ if (belongsToPackages == null ) {
275+ belongsToPackages = new TreeSet <String >();
276+ itemToPackages .put (key , belongsToPackages );
277+ }
278+
279+ Date existing = lastModifiedMap .get (key );
280+ if (existing == null ) {
281+ lastModifiedMap .put (key , lastModified );
282+ }
283+ else {
284+ if (!existing .equals (lastModified )) {
285+ String name ;
286+ if (item .getIncoming () instanceof Concept ) {
287+ name = key + " " + ((Concept ) item .getIncoming ()).getName ();
288+ }
289+ else if (item .getIncoming () instanceof OpenmrsMetadata ) {
290+ name = key + " " + ((OpenmrsMetadata ) item .getIncoming ()).getName ();
291+ }
292+ else {
293+ name = key ;
294+ }
295+ log .error (("Found inconsistent versions of " + name + " in " + filename + " (" + lastModified + ") vs " + belongsToPackages + " (" + existing + ")" ));
296+ return false ;
297+ }
298+ }
299+ belongsToPackages .add (filename );
300+ return true ;
301+ }
302+
303+ private Date getLastModified (Object object ) {
304+ if (object instanceof Auditable ) {
305+ Date dateChanged = ((Auditable ) object ).getDateChanged ();
306+ if (dateChanged != null ) {
307+ return dateChanged ;
308+ } else {
309+ return ((Auditable ) object ).getDateCreated ();
310+ }
311+ }
312+ else {
313+ throw new IllegalArgumentException ("object must be Auditable" );
314+ }
315+ }
316+
317+ private String getKey (ImportedItem item ) {
318+ try {
319+ return item .getIncomingClassSimpleName () + ":" + PropertyUtils .getProperty (item .getIncoming (), "uuid" );
320+ } catch (Exception e ) {
321+ throw new RuntimeException ("Couldn't get UUID from " + item .getIncoming ());
322+ }
323+ }
324+
325+ public int size () {
326+ return lastModifiedMap .size ();
327+ }
328+
329+ public Map <String , Set <String >> repeatedItems () {
330+ Map <String , Set <String >> repeatedItems = new TreeMap <String , Set <String >>();
331+ for (Map .Entry <String , Set <String >> candidate : itemToPackages .entrySet ()) {
332+ if (candidate .getValue ().size () > 1 ) {
333+ repeatedItems .put (candidate .getKey (), candidate .getValue ());
334+ }
335+ }
336+ return repeatedItems ;
337+ }
338+
339+ }
340+
341+ }
0 commit comments