diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/query/BindVisitor.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/query/BindVisitor.java index 8dc6221cfa8..bee3bda1ac1 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/query/BindVisitor.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/query/BindVisitor.java @@ -28,7 +28,6 @@ * for looser coupling. */ public class BindVisitor implements BindMarkerNodeVisitor { - private static final Calendar UTC = Calendar.getInstance(TimeZone.getTimeZone("UTC")); private static final Logger logger = Logger.getLogger(BindVisitor.class.getName()); // the statement to bind values to @@ -40,6 +39,9 @@ public class BindVisitor implements BindMarkerNodeVisitor { // Keep track of the parameter index private int parameterIndex = 1; + // Do not make static - Calendar is not thread-safe + private final Calendar UTC = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + /** * Public constructor * @param ps diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/SchemaConstants.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/SchemaConstants.java index 1b14927a365..94a50d7962e 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/SchemaConstants.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/SchemaConstants.java @@ -19,7 +19,4 @@ public class SchemaConstants { public static final String OBJECT_TYPE = "OBJECT_TYPE"; public static final String OBJECT_NAME = "OBJECT_NAME"; public static final String SCHEMA_NAME = "SCHEMA_NAME"; - - public static final Calendar UTC = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - } diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/JDBCConstants.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/JDBCConstants.java index 361824c2f17..b700b42e14f 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/JDBCConstants.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/JDBCConstants.java @@ -119,9 +119,11 @@ public class JDBCConstants { public static final String DEFAULT_TOKEN_SYSTEM = "default-token-system"; /** - * Calendar object to use while inserting Timestamp objects into the database. + * This Calendar object is not thread-safe! Use CalendarHelper#getCalendarForUTC() instead. */ - public static final Calendar UTC = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + // DO NOT USE + // public static final Calendar UTC = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + // DO NOT USE /** * Maps search parameter types to the currently supported list of modifiers for that type. diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/ReindexResourceDAO.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/ReindexResourceDAO.java index 5e0c156d783..b6983ea78e2 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/ReindexResourceDAO.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/ReindexResourceDAO.java @@ -33,6 +33,7 @@ import com.ibm.fhir.persistence.jdbc.dao.impl.ResourceDAOImpl; import com.ibm.fhir.persistence.jdbc.dto.ExtractedParameterValue; import com.ibm.fhir.persistence.jdbc.impl.ParameterTransactionDataImpl; +import com.ibm.fhir.persistence.jdbc.util.CalendarHelper; import com.ibm.fhir.persistence.jdbc.util.ParameterTableSupport; /** @@ -148,7 +149,7 @@ protected ResourceIndexRecord getResource(Instant reindexTstamp, Long logicalRes if (logicalResourceId != null) { // specific resource by logical resource ID (primary key) stmt.setLong(1, logicalResourceId); - stmt.setTimestamp(2, Timestamp.from(reindexTstamp)); + stmt.setTimestamp(2, Timestamp.from(reindexTstamp), CalendarHelper.getCalendarForUTC()); } ResultSet rs = stmt.executeQuery(); if (rs.next()) { @@ -208,13 +209,13 @@ protected ResourceIndexRecord getNextResource(SecureRandom random, Instant reind if (resourceTypeId != null && logicalId != null) { stmt.setInt(1, resourceTypeId); stmt.setString(2, logicalId); - stmt.setTimestamp(3, Timestamp.from(reindexTstamp)); + stmt.setTimestamp(3, Timestamp.from(reindexTstamp), CalendarHelper.getCalendarForUTC()); } else if (resourceTypeId != null) { stmt.setInt(1, resourceTypeId); - stmt.setTimestamp(2, Timestamp.from(reindexTstamp)); + stmt.setTimestamp(2, Timestamp.from(reindexTstamp), CalendarHelper.getCalendarForUTC()); stmt.setInt(3, offset); } else { - stmt.setTimestamp(1, Timestamp.from(reindexTstamp)); + stmt.setTimestamp(1, Timestamp.from(reindexTstamp), CalendarHelper.getCalendarForUTC()); stmt.setInt(2, offset); } ResultSet rs = stmt.executeQuery(); @@ -237,7 +238,7 @@ protected ResourceIndexRecord getNextResource(SecureRandom random, Instant reind + " AND reindex_txid = ? "; // make sure we have the txid we selected above try (PreparedStatement stmt = connection.prepareStatement(UPDATE)) { - stmt.setTimestamp(1, Timestamp.from(reindexTstamp)); + stmt.setTimestamp(1, Timestamp.from(reindexTstamp), CalendarHelper.getCalendarForUTC()); stmt.setLong(2, result.getTransactionId() + 1L); stmt.setLong(3, result.getLogicalResourceId()); stmt.setLong(4, result.getTransactionId()); diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FHIRDbDAOImpl.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FHIRDbDAOImpl.java index 14fbf7df14a..7c21e3be0ea 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FHIRDbDAOImpl.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FHIRDbDAOImpl.java @@ -32,6 +32,7 @@ import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDBCleanupException; import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDBConnectException; import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDataAccessException; +import com.ibm.fhir.persistence.jdbc.util.CalendarHelper; /** * This class is a root Data Access Object for managing JDBC access to the FHIR database. @@ -200,7 +201,7 @@ protected List runQuery(String sql, Object... searchArgs) // Inject arguments into the prepared stmt. for (int i = 0; i < searchArgs.length; i++) { if (searchArgs[i] instanceof Timestamp) { - stmt.setTimestamp(i + 1, (Timestamp) searchArgs[i], JDBCConstants.UTC); + stmt.setTimestamp(i + 1, (Timestamp) searchArgs[i], CalendarHelper.getCalendarForUTC()); } else { stmt.setObject(i + 1, searchArgs[i]); } @@ -263,7 +264,7 @@ protected int runCountQuery(String sql, Object... searchArgs) // Inject arguments into the prepared stmt. for (int i = 0; i < searchArgs.length; i++) { if (searchArgs[i] instanceof Timestamp) { - stmt.setTimestamp(i + 1, (Timestamp) searchArgs[i], JDBCConstants.UTC); + stmt.setTimestamp(i + 1, (Timestamp) searchArgs[i], CalendarHelper.getCalendarForUTC()); } else { stmt.setObject(i + 1, searchArgs[i]); } @@ -474,7 +475,7 @@ protected List runQuery_STR_VALUES(String sql, Object... searchArgs) // Inject arguments into the prepared stmt. for (int i = 0; i < searchArgs.length; i++) { if (searchArgs[i] instanceof Timestamp) { - stmt.setTimestamp(i + 1, (Timestamp) searchArgs[i], JDBCConstants.UTC); + stmt.setTimestamp(i + 1, (Timestamp) searchArgs[i], CalendarHelper.getCalendarForUTC()); } else { stmt.setObject(i + 1, searchArgs[i]); } diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FetchPayloadsForIdsDAO.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FetchPayloadsForIdsDAO.java index 56dce4ed1a4..abfd13cc0e6 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FetchPayloadsForIdsDAO.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FetchPayloadsForIdsDAO.java @@ -23,6 +23,7 @@ import com.ibm.fhir.persistence.ResourcePayload; import com.ibm.fhir.persistence.exception.FHIRPersistenceException; +import com.ibm.fhir.persistence.jdbc.util.CalendarHelper; /** * DAO to fetch the payload objects for a list of resource ids @@ -107,7 +108,7 @@ public void run(Connection c) throws FHIRPersistenceException { // we can save a ton of CPU. The stream is closed by ResultSet (according to the docs). ResultSet // will be closed when the PreparedStatement is closed String logicalId = rs.getString(1); - Instant lastUpdated = Instant.ofEpochMilli(rs.getTimestamp(2).getTime()); + Instant lastUpdated = rs.getTimestamp(2, CalendarHelper.getCalendarForUTC()).toInstant(); long resourceId = rs.getLong(3); InputStream is = new GZIPInputStream(rs.getBinaryStream(4)); ResourcePayload rp = new ResourcePayload(logicalId, lastUpdated, resourceId, is); diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FetchResourceChangesDAO.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FetchResourceChangesDAO.java index a1a2a0593a2..fbb6d700fcf 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FetchResourceChangesDAO.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FetchResourceChangesDAO.java @@ -23,13 +23,13 @@ import com.ibm.fhir.persistence.ResourceChangeLogRecord; import com.ibm.fhir.persistence.ResourceChangeLogRecord.ChangeType; import com.ibm.fhir.persistence.exception.FHIRPersistenceException; +import com.ibm.fhir.persistence.jdbc.util.CalendarHelper; /** * Simple DAO to read records from the RESOURCE_CHANGE_LOG table */ public class FetchResourceChangesDAO { private static final Logger logger = Logger.getLogger(FetchResourceChangesDAO.class.getName()); - private static final Calendar UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("UTC")); private final IDatabaseTranslator translator; private final String schemaName; @@ -114,7 +114,7 @@ public List run(Connection c) throws FHIRPersistenceExc try (PreparedStatement ps = c.prepareStatement(SQL)) { int a = 1; if (this.fromTstamp != null) { - ps.setTimestamp(a++, Timestamp.from(this.fromTstamp), UTC_CALENDAR); + ps.setTimestamp(a++, Timestamp.from(this.fromTstamp), CalendarHelper.getCalendarForUTC()); } if (this.afterResourceId != null) { @@ -135,7 +135,7 @@ public List run(Connection c) throws FHIRPersistenceExc default: throw new FHIRPersistenceException("Invalid ChangeType in change log"); // DBA can find the bad row if it ever happens } - ResourceChangeLogRecord record = new ResourceChangeLogRecord(rs.getString(2), rs.getString(3), rs.getInt(5), rs.getLong(1), rs.getTimestamp(4, UTC_CALENDAR).toInstant(), ct); + ResourceChangeLogRecord record = new ResourceChangeLogRecord(rs.getString(2), rs.getString(3), rs.getInt(5), rs.getLong(1), rs.getTimestamp(4, CalendarHelper.getCalendarForUTC()).toInstant(), ct); result.add(record); } } catch (SQLException x) { diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FetchResourcePayloadsDAO.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FetchResourcePayloadsDAO.java index 5ad5705b2d4..0879b9a75ec 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FetchResourcePayloadsDAO.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FetchResourcePayloadsDAO.java @@ -27,6 +27,7 @@ import com.ibm.fhir.persistence.ResourcePayload; import com.ibm.fhir.persistence.exception.FHIRPersistenceException; import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDataAccessException; +import com.ibm.fhir.persistence.jdbc.util.CalendarHelper; /** * DAO to fetch resource ids using a time range and optional current resource id as a filter. @@ -36,8 +37,6 @@ public class FetchResourcePayloadsDAO { private static final Logger logger = Logger.getLogger(FetchResourcePayloadsDAO.class.getName()); - private static final Calendar UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - // The FHIR data schema name private final String schemaName; @@ -118,19 +117,19 @@ public ResourcePayload run(Connection c) throws FHIRPersistenceException { // Set the variables marking the start point of the scan if (this.fromLastUpdated != null) { - ps.setTimestamp(a++, Timestamp.from(this.fromLastUpdated), UTC_CALENDAR); + ps.setTimestamp(a++, Timestamp.from(this.fromLastUpdated), CalendarHelper.getCalendarForUTC()); } // And where we want the scan to stop (e.g. exporting a limited time range) if (this.toLastUpdated != null) { - ps.setTimestamp(a++, Timestamp.from(this.toLastUpdated), UTC_CALENDAR); + ps.setTimestamp(a++, Timestamp.from(this.toLastUpdated), CalendarHelper.getCalendarForUTC()); } ResultSet rs = ps.executeQuery(); while (rs.next()) { // make sure we get the timestamp as a UTC value String logicalId = rs.getString(1); - Instant lastUpdated = rs.getTimestamp(2, UTC_CALENDAR).toInstant(); + Instant lastUpdated = rs.getTimestamp(2, CalendarHelper.getCalendarForUTC()).toInstant(); long resourceId = rs.getLong(3); InputStream is = new GZIPInputStream(rs.getBinaryStream(4)); result = new ResourcePayload(logicalId, lastUpdated, resourceId, is); @@ -192,12 +191,12 @@ public int count(Connection c) throws FHIRPersistenceException { // Set the variables marking the start point of the scan if (this.fromLastUpdated != null) { - ps.setTimestamp(a++, Timestamp.from(fromLastUpdated), UTC_CALENDAR); + ps.setTimestamp(a++, Timestamp.from(fromLastUpdated), CalendarHelper.getCalendarForUTC()); } // And where we want the scan to stop (e.g. exporting a limited time range) if (this.toLastUpdated != null) { - ps.setTimestamp(a++, Timestamp.from(this.toLastUpdated), UTC_CALENDAR); + ps.setTimestamp(a++, Timestamp.from(this.toLastUpdated), CalendarHelper.getCalendarForUTC()); } ResultSet rs = ps.executeQuery(); diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ParameterVisitorBatchDAO.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ParameterVisitorBatchDAO.java index 36fc5081f99..8302e3f4672 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ParameterVisitorBatchDAO.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ParameterVisitorBatchDAO.java @@ -7,7 +7,6 @@ package com.ibm.fhir.persistence.jdbc.dao.impl; import static com.ibm.fhir.config.FHIRConfiguration.PROPERTY_SEARCH_ENABLE_LEGACY_WHOLE_SYSTEM_SEARCH_PARAMS; -import static com.ibm.fhir.persistence.jdbc.JDBCConstants.UTC; import static com.ibm.fhir.search.SearchConstants.PROFILE; import static com.ibm.fhir.search.SearchConstants.SECURITY; import static com.ibm.fhir.search.SearchConstants.TAG; @@ -19,6 +18,7 @@ import java.sql.Timestamp; import java.sql.Types; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -40,6 +40,7 @@ import com.ibm.fhir.persistence.jdbc.dto.TokenParmVal; import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDataAccessException; import com.ibm.fhir.persistence.jdbc.impl.ParameterTransactionDataImpl; +import com.ibm.fhir.persistence.jdbc.util.CalendarHelper; import com.ibm.fhir.persistence.jdbc.util.CanonicalSupport; import com.ibm.fhir.schema.control.FhirSchemaConstants; import com.ibm.fhir.search.util.ReferenceValue; @@ -415,6 +416,7 @@ public void visit(DateParmVal param) throws FHIRPersistenceException { } private void setDateParms(PreparedStatement insert, int parameterNameId, Timestamp dateStart, Timestamp dateEnd) throws SQLException { + final Calendar UTC = CalendarHelper.getCalendarForUTC(); insert.setInt(1, parameterNameId); insert.setTimestamp(2, dateStart, UTC); insert.setTimestamp(3, dateEnd, UTC); diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ResourceDAOImpl.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ResourceDAOImpl.java index 37fe1e5de50..3d624879270 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ResourceDAOImpl.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ResourceDAOImpl.java @@ -8,7 +8,6 @@ import static com.ibm.fhir.persistence.jdbc.JDBCConstants.END; import static com.ibm.fhir.persistence.jdbc.JDBCConstants.THEN; -import static com.ibm.fhir.persistence.jdbc.JDBCConstants.UTC; import static com.ibm.fhir.persistence.jdbc.JDBCConstants.WHEN; import java.sql.CallableStatement; @@ -48,6 +47,7 @@ import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDataAccessException; import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceFKVException; import com.ibm.fhir.persistence.jdbc.impl.ParameterTransactionDataImpl; +import com.ibm.fhir.persistence.jdbc.util.CalendarHelper; import com.ibm.fhir.persistence.jdbc.util.ResourceTypesCache; import com.ibm.fhir.persistence.jdbc.util.ResourceTypesCacheUpdater; import com.ibm.fhir.persistence.jdbc.util.SqlQueryData; @@ -262,7 +262,7 @@ protected Resource createDTO(ResultSet resultSet) throws FHIRPersistenceDataAcce } resource.setId(resultSet.getLong(IDX_RESOURCE_ID)); resource.setLogicalResourceId(resultSet.getLong(IDX_LOGICAL_RESOURCE_ID)); - resource.setLastUpdated(resultSet.getTimestamp(IDX_LAST_UPDATED)); + resource.setLastUpdated(resultSet.getTimestamp(IDX_LAST_UPDATED, CalendarHelper.getCalendarForUTC())); resource.setLogicalId(resultSet.getString(IDX_LOGICAL_ID)); resource.setVersionId(resultSet.getInt(IDX_VERSION_ID)); resource.setDeleted(resultSet.getString(IDX_IS_DELETED).equals("Y") ? true : false); @@ -466,7 +466,7 @@ public List searchForIds(SqlQueryData queryData) throws FHIRPersistenceDat for (int i = 0; i < queryData.getBindVariables().size(); i++) { Object object = queryData.getBindVariables().get(i); if (object instanceof Timestamp) { - stmt.setTimestamp(i + 1, (Timestamp) object, JDBCConstants.UTC); + stmt.setTimestamp(i + 1, (Timestamp) object, CalendarHelper.getCalendarForUTC()); } else { stmt.setObject(i + 1, object); } @@ -582,7 +582,7 @@ public Resource insert(Resource resource, List paramete } lastUpdated = resource.getLastUpdated(); - stmt.setTimestamp(4, lastUpdated, UTC); + stmt.setTimestamp(4, lastUpdated, CalendarHelper.getCalendarForUTC()); stmt.setString(5, resource.isDeleted() ? "Y": "N"); stmt.setInt(6, resource.getVersionId()); stmt.setString(7, parameterHashB64); diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/RetrieveIndexDAO.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/RetrieveIndexDAO.java index 7c83f85af62..2d54cf91b17 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/RetrieveIndexDAO.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/RetrieveIndexDAO.java @@ -22,14 +22,13 @@ import com.ibm.fhir.database.utils.api.IDatabaseTranslator; import com.ibm.fhir.persistence.exception.FHIRPersistenceException; import com.ibm.fhir.persistence.jdbc.FHIRPersistenceJDBCCache; +import com.ibm.fhir.persistence.jdbc.util.CalendarHelper; /** * Simple DAO to retrieve index IDs (i.e. logical resource IDs) from the LOGICAL_RESOURCES table. */ public class RetrieveIndexDAO { private static final Logger logger = Logger.getLogger(RetrieveIndexDAO.class.getName()); - private static final Calendar UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - private final IDatabaseTranslator translator; private final String schemaName; private final String resourceTypeName; @@ -109,7 +108,7 @@ public List run(Connection c) throws FHIRPersistenceException { ps.setString(i++, resourceTypeName); } if (notModifiedAfter != null) { - ps.setTimestamp(i++, Timestamp.from(notModifiedAfter), UTC_CALENDAR); + ps.setTimestamp(i++, Timestamp.from(notModifiedAfter), CalendarHelper.getCalendarForUTC()); } if (afterLogicalResourceId != null) { ps.setLong(i++, afterLogicalResourceId); diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/derby/DerbyResourceDAO.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/derby/DerbyResourceDAO.java index 2325e8e28ff..58090b4c454 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/derby/DerbyResourceDAO.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/derby/DerbyResourceDAO.java @@ -6,8 +6,6 @@ package com.ibm.fhir.persistence.jdbc.derby; -import static com.ibm.fhir.persistence.jdbc.JDBCConstants.UTC; - import java.io.InputStream; import java.sql.Connection; import java.sql.PreparedStatement; @@ -15,6 +13,7 @@ import java.sql.SQLException; import java.sql.SQLIntegrityConstraintViolationException; import java.sql.Timestamp; +import java.util.Calendar; import java.util.List; import java.util.UUID; import java.util.logging.Level; @@ -40,6 +39,7 @@ import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDataAccessException; import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceFKVException; import com.ibm.fhir.persistence.jdbc.impl.ParameterTransactionDataImpl; +import com.ibm.fhir.persistence.jdbc.util.CalendarHelper; import com.ibm.fhir.persistence.jdbc.util.ParameterTableSupport; import com.ibm.fhir.persistence.jdbc.util.ResourceTypesCache; @@ -207,6 +207,7 @@ public Resource insert(Resource resource, List paramet public long storeResource(String tablePrefix, List parameters, String p_logical_id, InputStream p_payload, Timestamp p_last_updated, boolean p_is_deleted, String p_source_key, Integer p_version, String p_parameterHashB64, Connection conn, ParameterDAO parameterDao) throws Exception { + final Calendar UTC = CalendarHelper.getCalendarForUTC(); final String METHODNAME = "storeResource() for " + tablePrefix + " resource"; logger.entering(CLASSNAME, METHODNAME); diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/postgres/PostgresReindexResourceDAO.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/postgres/PostgresReindexResourceDAO.java index 8c18cac9252..6cf0de7528b 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/postgres/PostgresReindexResourceDAO.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/postgres/PostgresReindexResourceDAO.java @@ -13,6 +13,7 @@ import java.sql.SQLException; import java.sql.Timestamp; import java.time.Instant; +import java.util.Calendar; import java.util.logging.Level; import java.util.logging.Logger; @@ -26,6 +27,7 @@ import com.ibm.fhir.persistence.jdbc.dao.api.ParameterDAO; import com.ibm.fhir.persistence.jdbc.dao.api.ResourceIndexRecord; import com.ibm.fhir.persistence.jdbc.impl.ParameterTransactionDataImpl; +import com.ibm.fhir.persistence.jdbc.util.CalendarHelper; /** * PostgreSQL specialization of the DAO used to assist the reindex custom operation @@ -136,22 +138,23 @@ public ResourceIndexRecord getNextResource(SecureRandom random, Instant reindexT throw new IllegalArgumentException("logicalId specified without a resourceType"); } + final Calendar UTC = CalendarHelper.getCalendarForUTC(); try (PreparedStatement stmt = connection.prepareStatement(update)) { if (resourceTypeId != null && logicalId != null) { // specific resource - stmt.setTimestamp(1, Timestamp.from(reindexTstamp)); + stmt.setTimestamp(1, Timestamp.from(reindexTstamp), UTC); stmt.setInt(2, resourceTypeId); stmt.setString(3, logicalId); - stmt.setTimestamp(4, Timestamp.from(reindexTstamp)); + stmt.setTimestamp(4, Timestamp.from(reindexTstamp), UTC); } else if (resourceTypeId != null) { // limit to resource type - stmt.setTimestamp(1, Timestamp.from(reindexTstamp)); + stmt.setTimestamp(1, Timestamp.from(reindexTstamp), UTC); stmt.setInt(2, resourceTypeId); - stmt.setTimestamp(3, Timestamp.from(reindexTstamp)); + stmt.setTimestamp(3, Timestamp.from(reindexTstamp), UTC); } else { // any resource type - stmt.setTimestamp(1, Timestamp.from(reindexTstamp)); - stmt.setTimestamp(2, Timestamp.from(reindexTstamp)); + stmt.setTimestamp(1, Timestamp.from(reindexTstamp), UTC); + stmt.setTimestamp(2, Timestamp.from(reindexTstamp), UTC); } stmt.execute(); diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/postgres/PostgresResourceDAO.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/postgres/PostgresResourceDAO.java index 788bf630e17..5f4be728923 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/postgres/PostgresResourceDAO.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/postgres/PostgresResourceDAO.java @@ -6,8 +6,6 @@ package com.ibm.fhir.persistence.jdbc.postgres; -import static com.ibm.fhir.persistence.jdbc.JDBCConstants.UTC; - import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; @@ -40,6 +38,7 @@ import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDataAccessException; import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceFKVException; import com.ibm.fhir.persistence.jdbc.impl.ParameterTransactionDataImpl; +import com.ibm.fhir.persistence.jdbc.util.CalendarHelper; import com.ibm.fhir.persistence.jdbc.util.ResourceTypesCache; /** @@ -101,7 +100,7 @@ public Resource insert(Resource resource, List paramete stmt.setBinaryStream(3, resource.getDataStream().inputStream()); lastUpdated = resource.getLastUpdated(); - stmt.setTimestamp(4, lastUpdated, UTC); + stmt.setTimestamp(4, lastUpdated, CalendarHelper.getCalendarForUTC()); stmt.setString(5, resource.isDeleted() ? "Y": "N"); stmt.setString(6, UUID.randomUUID().toString()); stmt.setInt(7, resource.getVersionId()); diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/postgres/PostgresResourceNoProcDAO.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/postgres/PostgresResourceNoProcDAO.java index 43d5e1d9ccc..44004b5a081 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/postgres/PostgresResourceNoProcDAO.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/postgres/PostgresResourceNoProcDAO.java @@ -6,8 +6,6 @@ package com.ibm.fhir.persistence.jdbc.postgres; -import static com.ibm.fhir.persistence.jdbc.JDBCConstants.UTC; - import java.io.InputStream; import java.sql.CallableStatement; import java.sql.Connection; @@ -17,6 +15,7 @@ import java.sql.SQLIntegrityConstraintViolationException; import java.sql.Timestamp; import java.sql.Types; +import java.util.Calendar; import java.util.List; import java.util.UUID; import java.util.logging.Level; @@ -41,6 +40,7 @@ import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDataAccessException; import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceFKVException; import com.ibm.fhir.persistence.jdbc.impl.ParameterTransactionDataImpl; +import com.ibm.fhir.persistence.jdbc.util.CalendarHelper; import com.ibm.fhir.persistence.jdbc.util.ParameterTableSupport; import com.ibm.fhir.persistence.jdbc.util.ResourceTypesCache; @@ -191,6 +191,7 @@ public long storeResource(String tablePrefix, List para final String METHODNAME = "storeResource() for " + tablePrefix + " resource"; logger.entering(CLASSNAME, METHODNAME); + final Calendar UTC = CalendarHelper.getCalendarForUTC(); Long v_logical_resource_id = null; Long v_current_resource_id = null; Long v_resource_id = null; diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/CalendarHelper.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/CalendarHelper.java new file mode 100644 index 00000000000..1e450ac9cc2 --- /dev/null +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/CalendarHelper.java @@ -0,0 +1,62 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.persistence.jdbc.util; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.Calendar; +import java.util.TimeZone; + +/** + * Helper for time and calendar-related functions related to JDBC statements and results + */ +public class CalendarHelper { + // Calendar is not thread-safe, but is non-trivial to construct, so we hold in thread-local + private static final ThreadLocal UTC_CALENDAR = new ThreadLocal() { + protected Calendar initialValue() { + return Calendar.getInstance(TimeZone.getTimeZone("UTC")); + }; + }; + + /** + * Get a thread-specific instance of a calendar configured for UTC. + * @return + */ + public static Calendar getCalendarForUTC() { + return UTC_CALENDAR.get(); + } + + /** + * Helper to obtain a UTC timestamp from a JDBC {@link ResultSet} + * @param rs + * @param col + * @return + * @throws SQLException + */ + public static Timestamp getTimestampUTC(ResultSet rs, int col) throws SQLException { + return rs.getTimestamp(col, getCalendarForUTC()); + } + + /** + * Helper to set a UTC timestamp value in a JDBC {@link PreparedStatement} + * Supports null handling for ts. + * @param ps + * @param col + * @param ts + * @throws SQLException + */ + public static void setTimestampUTC(PreparedStatement ps, int col, Timestamp ts) throws SQLException { + if (ts != null) { + ps.setTimestamp(col, ts, getCalendarForUTC()); + } else { + ps.setNull(col, Types.TIMESTAMP); + } + } +} \ No newline at end of file diff --git a/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/control/InitializeLogicalResourceDenorms.java b/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/control/InitializeLogicalResourceDenorms.java index 13c906b411e..c8a14214060 100644 --- a/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/control/InitializeLogicalResourceDenorms.java +++ b/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/control/InitializeLogicalResourceDenorms.java @@ -12,6 +12,8 @@ import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; +import java.util.Calendar; +import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; @@ -31,6 +33,9 @@ public class InitializeLogicalResourceDenorms implements IDatabaseStatement { private static final Logger logger = Logger.getLogger(InitializeLogicalResourceDenorms.class.getName()); private final String schemaName; private final String resourceTypeName; + + // Do not make static. Calendar is not thread-safe + private final Calendar UTC = Calendar.getInstance(TimeZone.getTimeZone("UTC")); /** * Public constructor @@ -101,7 +106,7 @@ private void runForDerby(IDatabaseTranslator translator, Connection c) { while (rs.next()) { long logicalResourceId = rs.getLong(1); String isDeleted = rs.getString(2); - Timestamp lastUpdated = rs.getTimestamp(3, SchemaConstants.UTC); + Timestamp lastUpdated = rs.getTimestamp(3, UTC); int versionId = rs.getInt(4); if (logger.isLoggable(Level.FINEST)) { @@ -113,7 +118,7 @@ private void runForDerby(IDatabaseTranslator translator, Connection c) { + " WHERE logical_resource_id = " + logicalResourceId); } updateStatement.setString(1, isDeleted); - updateStatement.setTimestamp(2, lastUpdated, SchemaConstants.UTC); + updateStatement.setTimestamp(2, lastUpdated, UTC); updateStatement.setInt(3, versionId); updateStatement.setLong(4, logicalResourceId); updateStatement.addBatch(); diff --git a/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/control/MigrateV0014LogicalResourceIsDeletedLastUpdated.java b/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/control/MigrateV0014LogicalResourceIsDeletedLastUpdated.java index d8334916d10..4a4b05dc95f 100644 --- a/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/control/MigrateV0014LogicalResourceIsDeletedLastUpdated.java +++ b/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/control/MigrateV0014LogicalResourceIsDeletedLastUpdated.java @@ -12,6 +12,8 @@ import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; +import java.util.Calendar; +import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; @@ -41,6 +43,9 @@ public class MigrateV0014LogicalResourceIsDeletedLastUpdated implements IDatabas // The database id for the resource type private final int resourceTypeId; + // Do not make static. Calendar is not thread-safe + private final Calendar UTC = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + /** * Public constructor * @param schemaName @@ -115,7 +120,7 @@ private void runForDerby(IDatabaseTranslator translator, Connection c) { while (rs.next()) { long logicalResourceId = rs.getLong(1); String isDeleted = rs.getString(2); - Timestamp lastUpdated = rs.getTimestamp(3, SchemaConstants.UTC); + Timestamp lastUpdated = rs.getTimestamp(3, UTC); if (LOG.isLoggable(Level.FINEST)) { // log the update in a form which is useful for debugging @@ -125,7 +130,7 @@ private void runForDerby(IDatabaseTranslator translator, Connection c) { + " WHERE logical_resource_id = " + logicalResourceId); } updateStatement.setString(1, isDeleted); - updateStatement.setTimestamp(2, lastUpdated, SchemaConstants.UTC); + updateStatement.setTimestamp(2, lastUpdated, UTC); updateStatement.setLong(3, logicalResourceId); updateStatement.addBatch();