Skip to content

Commit

Permalink
Add UCUM support (#1824)
Browse files Browse the repository at this point in the history
* Add UCUM support

* Add changelog

* Some cleanup

* Test fix

* Add flywayDB callback

* Add hooks to schema migrator
  • Loading branch information
jamesagnew committed Apr 30, 2020
1 parent f94f2fd commit 3d5a8bb
Show file tree
Hide file tree
Showing 19 changed files with 540 additions and 98 deletions.
106 changes: 106 additions & 0 deletions hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package ca.uhn.fhir.util;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BOMInputStream;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.util.function.Function;
import java.util.zip.GZIPInputStream;

/**
* Use this API with caution, it may change!
*/
public class ClasspathUtil {

private static final Logger ourLog = LoggerFactory.getLogger(ClasspathUtil.class);

public static String loadResource(String theClasspath) {
Function<InputStream, InputStream> streamTransform = t -> t;
return loadResource(theClasspath, streamTransform);
}

/**
* Load a classpath resource, throw an {@link InternalErrorException} if not found
*/
@Nonnull
public static InputStream loadResourceAsStream(String theClasspath) {
InputStream retVal = ClasspathUtil.class.getResourceAsStream(theClasspath);
if (retVal == null) {
throw new InternalErrorException("Unable to find classpath resource: " + theClasspath);
}
return retVal;
}

/**
* Load a classpath resource, throw an {@link InternalErrorException} if not found
*/
@Nonnull
public static String loadResource(String theClasspath, Function<InputStream, InputStream> theStreamTransform) {
InputStream stream = ClasspathUtil.class.getResourceAsStream(theClasspath);
try {
if (stream == null) {
throw new IOException("Unable to find classpath resource: " + theClasspath);
}
try {
InputStream newStream = theStreamTransform.apply(stream);
return IOUtils.toString(newStream, Charsets.UTF_8);
} finally {
stream.close();
}
} catch (IOException e) {
throw new InternalErrorException(e);
}
}

@Nonnull
public static String loadCompressedResource(String theClasspath) {
Function<InputStream, InputStream> streamTransform = t -> {
try {
return new GZIPInputStream(t);
} catch (IOException e) {
throw new InternalErrorException(e);
}
};
return loadResource(theClasspath, streamTransform);
}

@Nonnull
public static <T extends IBaseResource> T loadResource(FhirContext theCtx, Class<T> theType, String theClasspath) {
String raw = loadResource(theClasspath);
return EncodingEnum.detectEncodingNoDefault(raw).newParser(theCtx).parseResource(theType, raw);
}

public static void close(InputStream theInput) {
try {
if (theInput != null) {
theInput.close();
}
} catch (IOException e) {
ourLog.debug("Closing InputStream threw exception", e);
}
}

public static Function<InputStream, InputStream> withBom() {
return t -> new BOMInputStream(t);
}

public static byte[] loadResourceAsByteArray(String theClasspath) {
InputStream stream = loadResourceAsStream(theClasspath);
try {
return IOUtils.toByteArray(stream);
} catch (IOException e) {
throw new InternalErrorException(e);
} finally {
close(stream);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BOMInputStream;
import ca.uhn.fhir.util.ClasspathUtil;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
Expand All @@ -41,10 +39,7 @@
import javax.xml.validation.Validator;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -152,20 +147,9 @@ private Schema loadSchema() {
Source loadXml(String theSchemaName) {
String pathToBase = myCtx.getVersion().getPathToSchemaDefinitions() + '/' + theSchemaName;
ourLog.debug("Going to load resource: {}", pathToBase);
try (InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase)) {
if (baseIs == null) {
throw new InternalErrorException("Schema not found. " + RESOURCES_JAR_NOTE);
}
try (BOMInputStream bomInputStream = new BOMInputStream(baseIs, false)) {
try (InputStreamReader baseReader = new InputStreamReader(bomInputStream, StandardCharsets.UTF_8)) {
// Buffer so that we can close the input stream
String contents = IOUtils.toString(baseReader);
return new StreamSource(new StringReader(contents), null);
}
}
} catch (IOException e) {
throw new InternalErrorException(e);
}

String contents = ClasspathUtil.loadResource(pathToBase, ClasspathUtil.withBom());
return new StreamSource(new StringReader(contents), null);
}

@Override
Expand All @@ -188,16 +172,8 @@ public LSInput resolveResource(String theType, String theNamespaceURI, String th

ourLog.debug("Loading referenced schema file: " + pathToBase);

try (InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase)) {
if (baseIs == null) {
throw new InternalErrorException("Schema file not found: " + pathToBase);
}

byte[] bytes = IOUtils.toByteArray(baseIs);
input.setByteStream(new ByteArrayInputStream(bytes));
} catch (IOException e) {
throw new InternalErrorException(e);
}
byte[] bytes = ClasspathUtil.loadResourceAsByteArray(pathToBase);
input.setByteStream(new ByteArrayInputStream(bytes));
return input;

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package ca.uhn.fhir.util;

import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import static org.junit.Assert.*;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;

public class ClasspathUtilTest {

@Test
public void testLoadResourceNotFound() {
try {
ClasspathUtil.loadResource("/FOOOOOO");
} catch (InternalErrorException e) {
assertEquals("Unable to find classpath resource: /FOOOOOO", e.getMessage());
}
}

@Test
public void testLoadResourceAsStreamNotFound() {
try {
ClasspathUtil.loadResourceAsStream("/FOOOOOO");
} catch (InternalErrorException e) {
assertEquals("Unable to find classpath resource: /FOOOOOO", e.getMessage());
}
}

/**
* Should not throw any exception
*/
@Test
public void testClose_Null() {
ClasspathUtil.close(null);
}

/**
* Should not throw any exception
*/
@Test
public void testClose_Ok() {
ClasspathUtil.close(new ByteArrayInputStream(new byte[]{0,1,2}));
}


/**
* Should not throw any exception
*/
@Test
public void testClose_ThrowException() throws IOException {
InputStream is = mock(InputStream.class);
doThrow(new IOException("FOO")).when(is).close();
ClasspathUtil.close(is);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
type: add
issue: 1824
title: Native support for UCUM has been added to the validation stack, meaning that UCUM codes can be validated
at runtime without the need for any external validation.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<li>Hibernate Validator (JPA): 5.4.2.Final -&gt; 6.1.3.Final</li>
<li>Guava (JPA): 28.0 -&gt; 28.2</li>
<li>Spring Boot (Boot): 2.2.0.RELEASE -&gt; 2.2.6.RELEASE</li>
<li>FlywayDB (JPA) 6.1.0 -&gt; 6.4.1</li>
</ul>"
- item:
issue: "1583"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,17 @@ The following table lists vocabulary that is validated by this module:
added in the future, please get in touch if you would like to help.
</td>
</tr>
<tr>
<td>Unified Codes for Units of Measure (UCUM)</td>
<td>
ValueSet: <code><a href="http://hl7.org/fhir/ValueSet/ucum-units">(...)/ValueSet/ucum-units</a></code>
<br/>
CodeSystem: <code>http://unitsofmeasure.org</code>
</td>
<td>
Codes are validated using the UcumEssenceService provided by the <a href="https://github.com/FHIR/Ucum-java">UCUM Java</a> library.
</td>
</tr>
</tbody>
</table>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@
*/

import ca.uhn.fhir.jpa.migrate.taskdef.BaseTask;
import org.apache.commons.lang3.Validate;
import org.flywaydb.core.api.callback.Callback;

import javax.annotation.Nonnull;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

Expand All @@ -34,6 +38,17 @@ public abstract class BaseMigrator implements IMigrator {
private DriverTypeEnum myDriverType;
private DataSource myDataSource;
private List<BaseTask.ExecutedStatement> myExecutedStatements = new ArrayList<>();
private List<Callback> myCallbacks = Collections.emptyList();

@Nonnull
public List<Callback> getCallbacks() {
return myCallbacks;
}

public void setCallbacks(@Nonnull List<Callback> theCallbacks) {
Validate.notNull(theCallbacks);
myCallbacks = theCallbacks;
}

public DataSource getDataSource() {
return myDataSource;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.google.common.annotations.VisibleForTesting;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.MigrationInfoService;
import org.flywaydb.core.api.callback.Callback;
import org.flywaydb.core.api.migration.JavaMigration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -79,6 +80,7 @@ private Flyway initFlyway(DriverTypeEnum.ConnectionProperties theConnectionPrope
.baselineOnMigrate(true)
.outOfOrder(isOutOfOrderPermitted())
.javaMigrations(myTasks.toArray(new JavaMigration[0]))
.callbacks(getCallbacks().toArray(new Callback[0]))
.load();
for (FlywayMigration task : myTasks) {
task.setConnectionProperties(theConnectionProperties);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,6 @@ public void addTasks(List<BaseTask> theTasks) {
public void setNoColumnShrink(boolean theNoColumnShrink) {
myNoColumnShrink = theNoColumnShrink;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,31 @@
import ca.uhn.fhir.jpa.migrate.taskdef.BaseTask;
import org.flywaydb.core.api.MigrationInfo;
import org.flywaydb.core.api.MigrationInfoService;
import org.flywaydb.core.api.callback.Callback;
import org.hibernate.cfg.AvailableSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Properties;

public class SchemaMigrator {
private static final Logger ourLog = LoggerFactory.getLogger(SchemaMigrator.class);
public static final String HAPI_FHIR_MIGRATION_TABLENAME = "FLY_HFJ_MIGRATION";
private static final Logger ourLog = LoggerFactory.getLogger(SchemaMigrator.class);
private final DataSource myDataSource;
private final boolean mySkipValidation;
private final String myMigrationTableName;
private final List<BaseTask> myMigrationTasks;
private boolean myDontUseFlyway;
private boolean myOutOfOrderPermitted;
private DriverTypeEnum myDriverType;
private List<Callback> myCallbacks = Collections.emptyList();

/**
* Constructor
Expand All @@ -61,6 +65,11 @@ public SchemaMigrator(String theMigrationTableName, DataSource theDataSource, Pr
}
}

public void setCallbacks(List<Callback> theCallbacks) {
Assert.notNull(theCallbacks);
myCallbacks = theCallbacks;
}

public void setDontUseFlyway(boolean theDontUseFlyway) {
myDontUseFlyway = theDontUseFlyway;
}
Expand Down Expand Up @@ -110,6 +119,7 @@ private BaseMigrator newMigrator() {
migrator.setOutOfOrderPermitted(myOutOfOrderPermitted);
}
migrator.addTasks(myMigrationTasks);
migrator.setCallbacks(myCallbacks);
return migrator;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@

public class InitializeSchemaTask extends BaseTask {
private static final Logger ourLog = LoggerFactory.getLogger(InitializeSchemaTask.class);
public static final String DESCRIPTION_PREFIX = "Initialize schema for ";

private final ISchemaInitializationProvider mySchemaInitializationProvider;

public InitializeSchemaTask(String theProductVersion, String theSchemaVersion, ISchemaInitializationProvider theSchemaInitializationProvider) {
super(theProductVersion, theSchemaVersion);
mySchemaInitializationProvider = theSchemaInitializationProvider;
setDescription("Initialize schema for " + mySchemaInitializationProvider.getSchemaDescription());
setDescription(DESCRIPTION_PREFIX + mySchemaInitializationProvider.getSchemaDescription());
}

@Override
Expand Down

0 comments on commit 3d5a8bb

Please sign in to comment.