Skip to content

Commit af70782

Browse files
authored
Merge pull request #2748 from IBM/issue-2747-utc-calendar
issue-2747 - thread safety for JDBC get/set timestamp UTC
2 parents ad00a02 + 514bb92 commit af70782

18 files changed

+130
-50
lines changed

fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/query/BindVisitor.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
* for looser coupling.
2929
*/
3030
public class BindVisitor implements BindMarkerNodeVisitor {
31-
private static final Calendar UTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
3231
private static final Logger logger = Logger.getLogger(BindVisitor.class.getName());
3332

3433
// the statement to bind values to
@@ -40,6 +39,9 @@ public class BindVisitor implements BindMarkerNodeVisitor {
4039
// Keep track of the parameter index
4140
private int parameterIndex = 1;
4241

42+
// Do not make static - Calendar is not thread-safe
43+
private final Calendar UTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
44+
4345
/**
4446
* Public constructor
4547
* @param ps

fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/SchemaConstants.java

-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,4 @@ public class SchemaConstants {
1919
public static final String OBJECT_TYPE = "OBJECT_TYPE";
2020
public static final String OBJECT_NAME = "OBJECT_NAME";
2121
public static final String SCHEMA_NAME = "SCHEMA_NAME";
22-
23-
public static final Calendar UTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
24-
2522
}

fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/JDBCConstants.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,11 @@ public class JDBCConstants {
119119
public static final String DEFAULT_TOKEN_SYSTEM = "default-token-system";
120120

121121
/**
122-
* Calendar object to use while inserting Timestamp objects into the database.
122+
* This Calendar object is not thread-safe! Use CalendarHelper#getCalendarForUTC() instead.
123123
*/
124-
public static final Calendar UTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
124+
// DO NOT USE
125+
// public static final Calendar UTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
126+
// DO NOT USE
125127

126128
/**
127129
* Maps search parameter types to the currently supported list of modifiers for that type.

fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/ReindexResourceDAO.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.ibm.fhir.persistence.jdbc.dao.impl.ResourceDAOImpl;
3434
import com.ibm.fhir.persistence.jdbc.dto.ExtractedParameterValue;
3535
import com.ibm.fhir.persistence.jdbc.impl.ParameterTransactionDataImpl;
36+
import com.ibm.fhir.persistence.jdbc.util.CalendarHelper;
3637
import com.ibm.fhir.persistence.jdbc.util.ParameterTableSupport;
3738

3839
/**
@@ -148,7 +149,7 @@ protected ResourceIndexRecord getResource(Instant reindexTstamp, Long logicalRes
148149
if (logicalResourceId != null) {
149150
// specific resource by logical resource ID (primary key)
150151
stmt.setLong(1, logicalResourceId);
151-
stmt.setTimestamp(2, Timestamp.from(reindexTstamp));
152+
stmt.setTimestamp(2, Timestamp.from(reindexTstamp), CalendarHelper.getCalendarForUTC());
152153
}
153154
ResultSet rs = stmt.executeQuery();
154155
if (rs.next()) {
@@ -208,13 +209,13 @@ protected ResourceIndexRecord getNextResource(SecureRandom random, Instant reind
208209
if (resourceTypeId != null && logicalId != null) {
209210
stmt.setInt(1, resourceTypeId);
210211
stmt.setString(2, logicalId);
211-
stmt.setTimestamp(3, Timestamp.from(reindexTstamp));
212+
stmt.setTimestamp(3, Timestamp.from(reindexTstamp), CalendarHelper.getCalendarForUTC());
212213
} else if (resourceTypeId != null) {
213214
stmt.setInt(1, resourceTypeId);
214-
stmt.setTimestamp(2, Timestamp.from(reindexTstamp));
215+
stmt.setTimestamp(2, Timestamp.from(reindexTstamp), CalendarHelper.getCalendarForUTC());
215216
stmt.setInt(3, offset);
216217
} else {
217-
stmt.setTimestamp(1, Timestamp.from(reindexTstamp));
218+
stmt.setTimestamp(1, Timestamp.from(reindexTstamp), CalendarHelper.getCalendarForUTC());
218219
stmt.setInt(2, offset);
219220
}
220221
ResultSet rs = stmt.executeQuery();
@@ -237,7 +238,7 @@ protected ResourceIndexRecord getNextResource(SecureRandom random, Instant reind
237238
+ " AND reindex_txid = ? "; // make sure we have the txid we selected above
238239

239240
try (PreparedStatement stmt = connection.prepareStatement(UPDATE)) {
240-
stmt.setTimestamp(1, Timestamp.from(reindexTstamp));
241+
stmt.setTimestamp(1, Timestamp.from(reindexTstamp), CalendarHelper.getCalendarForUTC());
241242
stmt.setLong(2, result.getTransactionId() + 1L);
242243
stmt.setLong(3, result.getLogicalResourceId());
243244
stmt.setLong(4, result.getTransactionId());

fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FHIRDbDAOImpl.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDBCleanupException;
3333
import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDBConnectException;
3434
import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDataAccessException;
35+
import com.ibm.fhir.persistence.jdbc.util.CalendarHelper;
3536

3637
/**
3738
* This class is a root Data Access Object for managing JDBC access to the FHIR database.
@@ -200,7 +201,7 @@ protected List<Resource> runQuery(String sql, Object... searchArgs)
200201
// Inject arguments into the prepared stmt.
201202
for (int i = 0; i < searchArgs.length; i++) {
202203
if (searchArgs[i] instanceof Timestamp) {
203-
stmt.setTimestamp(i + 1, (Timestamp) searchArgs[i], JDBCConstants.UTC);
204+
stmt.setTimestamp(i + 1, (Timestamp) searchArgs[i], CalendarHelper.getCalendarForUTC());
204205
} else {
205206
stmt.setObject(i + 1, searchArgs[i]);
206207
}
@@ -263,7 +264,7 @@ protected int runCountQuery(String sql, Object... searchArgs)
263264
// Inject arguments into the prepared stmt.
264265
for (int i = 0; i < searchArgs.length; i++) {
265266
if (searchArgs[i] instanceof Timestamp) {
266-
stmt.setTimestamp(i + 1, (Timestamp) searchArgs[i], JDBCConstants.UTC);
267+
stmt.setTimestamp(i + 1, (Timestamp) searchArgs[i], CalendarHelper.getCalendarForUTC());
267268
} else {
268269
stmt.setObject(i + 1, searchArgs[i]);
269270
}
@@ -474,7 +475,7 @@ protected List<String> runQuery_STR_VALUES(String sql, Object... searchArgs)
474475
// Inject arguments into the prepared stmt.
475476
for (int i = 0; i < searchArgs.length; i++) {
476477
if (searchArgs[i] instanceof Timestamp) {
477-
stmt.setTimestamp(i + 1, (Timestamp) searchArgs[i], JDBCConstants.UTC);
478+
stmt.setTimestamp(i + 1, (Timestamp) searchArgs[i], CalendarHelper.getCalendarForUTC());
478479
} else {
479480
stmt.setObject(i + 1, searchArgs[i]);
480481
}

fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FetchPayloadsForIdsDAO.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import com.ibm.fhir.persistence.ResourcePayload;
2525
import com.ibm.fhir.persistence.exception.FHIRPersistenceException;
26+
import com.ibm.fhir.persistence.jdbc.util.CalendarHelper;
2627

2728
/**
2829
* DAO to fetch the payload objects for a list of resource ids
@@ -107,7 +108,7 @@ public void run(Connection c) throws FHIRPersistenceException {
107108
// we can save a ton of CPU. The stream is closed by ResultSet (according to the docs). ResultSet
108109
// will be closed when the PreparedStatement is closed
109110
String logicalId = rs.getString(1);
110-
Instant lastUpdated = Instant.ofEpochMilli(rs.getTimestamp(2).getTime());
111+
Instant lastUpdated = rs.getTimestamp(2, CalendarHelper.getCalendarForUTC()).toInstant();
111112
long resourceId = rs.getLong(3);
112113
InputStream is = new GZIPInputStream(rs.getBinaryStream(4));
113114
ResourcePayload rp = new ResourcePayload(logicalId, lastUpdated, resourceId, is);

fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FetchResourceChangesDAO.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@
2323
import com.ibm.fhir.persistence.ResourceChangeLogRecord;
2424
import com.ibm.fhir.persistence.ResourceChangeLogRecord.ChangeType;
2525
import com.ibm.fhir.persistence.exception.FHIRPersistenceException;
26+
import com.ibm.fhir.persistence.jdbc.util.CalendarHelper;
2627

2728
/**
2829
* Simple DAO to read records from the RESOURCE_CHANGE_LOG table
2930
*/
3031
public class FetchResourceChangesDAO {
3132
private static final Logger logger = Logger.getLogger(FetchResourceChangesDAO.class.getName());
32-
private static final Calendar UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
3333

3434
private final IDatabaseTranslator translator;
3535
private final String schemaName;
@@ -114,7 +114,7 @@ public List<ResourceChangeLogRecord> run(Connection c) throws FHIRPersistenceExc
114114
try (PreparedStatement ps = c.prepareStatement(SQL)) {
115115
int a = 1;
116116
if (this.fromTstamp != null) {
117-
ps.setTimestamp(a++, Timestamp.from(this.fromTstamp), UTC_CALENDAR);
117+
ps.setTimestamp(a++, Timestamp.from(this.fromTstamp), CalendarHelper.getCalendarForUTC());
118118
}
119119

120120
if (this.afterResourceId != null) {
@@ -135,7 +135,7 @@ public List<ResourceChangeLogRecord> run(Connection c) throws FHIRPersistenceExc
135135
default:
136136
throw new FHIRPersistenceException("Invalid ChangeType in change log"); // DBA can find the bad row if it ever happens
137137
}
138-
ResourceChangeLogRecord record = new ResourceChangeLogRecord(rs.getString(2), rs.getString(3), rs.getInt(5), rs.getLong(1), rs.getTimestamp(4, UTC_CALENDAR).toInstant(), ct);
138+
ResourceChangeLogRecord record = new ResourceChangeLogRecord(rs.getString(2), rs.getString(3), rs.getInt(5), rs.getLong(1), rs.getTimestamp(4, CalendarHelper.getCalendarForUTC()).toInstant(), ct);
139139
result.add(record);
140140
}
141141
} catch (SQLException x) {

fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FetchResourcePayloadsDAO.java

+6-7
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.ibm.fhir.persistence.ResourcePayload;
2828
import com.ibm.fhir.persistence.exception.FHIRPersistenceException;
2929
import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDataAccessException;
30+
import com.ibm.fhir.persistence.jdbc.util.CalendarHelper;
3031

3132
/**
3233
* DAO to fetch resource ids using a time range and optional current resource id as a filter.
@@ -36,8 +37,6 @@
3637
public class FetchResourcePayloadsDAO {
3738
private static final Logger logger = Logger.getLogger(FetchResourcePayloadsDAO.class.getName());
3839

39-
private static final Calendar UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
40-
4140
// The FHIR data schema name
4241
private final String schemaName;
4342

@@ -118,19 +117,19 @@ public ResourcePayload run(Connection c) throws FHIRPersistenceException {
118117

119118
// Set the variables marking the start point of the scan
120119
if (this.fromLastUpdated != null) {
121-
ps.setTimestamp(a++, Timestamp.from(this.fromLastUpdated), UTC_CALENDAR);
120+
ps.setTimestamp(a++, Timestamp.from(this.fromLastUpdated), CalendarHelper.getCalendarForUTC());
122121
}
123122

124123
// And where we want the scan to stop (e.g. exporting a limited time range)
125124
if (this.toLastUpdated != null) {
126-
ps.setTimestamp(a++, Timestamp.from(this.toLastUpdated), UTC_CALENDAR);
125+
ps.setTimestamp(a++, Timestamp.from(this.toLastUpdated), CalendarHelper.getCalendarForUTC());
127126
}
128127

129128
ResultSet rs = ps.executeQuery();
130129
while (rs.next()) {
131130
// make sure we get the timestamp as a UTC value
132131
String logicalId = rs.getString(1);
133-
Instant lastUpdated = rs.getTimestamp(2, UTC_CALENDAR).toInstant();
132+
Instant lastUpdated = rs.getTimestamp(2, CalendarHelper.getCalendarForUTC()).toInstant();
134133
long resourceId = rs.getLong(3);
135134
InputStream is = new GZIPInputStream(rs.getBinaryStream(4));
136135
result = new ResourcePayload(logicalId, lastUpdated, resourceId, is);
@@ -192,12 +191,12 @@ public int count(Connection c) throws FHIRPersistenceException {
192191

193192
// Set the variables marking the start point of the scan
194193
if (this.fromLastUpdated != null) {
195-
ps.setTimestamp(a++, Timestamp.from(fromLastUpdated), UTC_CALENDAR);
194+
ps.setTimestamp(a++, Timestamp.from(fromLastUpdated), CalendarHelper.getCalendarForUTC());
196195
}
197196

198197
// And where we want the scan to stop (e.g. exporting a limited time range)
199198
if (this.toLastUpdated != null) {
200-
ps.setTimestamp(a++, Timestamp.from(this.toLastUpdated), UTC_CALENDAR);
199+
ps.setTimestamp(a++, Timestamp.from(this.toLastUpdated), CalendarHelper.getCalendarForUTC());
201200
}
202201

203202
ResultSet rs = ps.executeQuery();

fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ParameterVisitorBatchDAO.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package com.ibm.fhir.persistence.jdbc.dao.impl;
88

99
import static com.ibm.fhir.config.FHIRConfiguration.PROPERTY_SEARCH_ENABLE_LEGACY_WHOLE_SYSTEM_SEARCH_PARAMS;
10-
import static com.ibm.fhir.persistence.jdbc.JDBCConstants.UTC;
1110
import static com.ibm.fhir.search.SearchConstants.PROFILE;
1211
import static com.ibm.fhir.search.SearchConstants.SECURITY;
1312
import static com.ibm.fhir.search.SearchConstants.TAG;
@@ -19,6 +18,7 @@
1918
import java.sql.Timestamp;
2019
import java.sql.Types;
2120
import java.util.ArrayList;
21+
import java.util.Calendar;
2222
import java.util.List;
2323
import java.util.logging.Level;
2424
import java.util.logging.Logger;
@@ -40,6 +40,7 @@
4040
import com.ibm.fhir.persistence.jdbc.dto.TokenParmVal;
4141
import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDataAccessException;
4242
import com.ibm.fhir.persistence.jdbc.impl.ParameterTransactionDataImpl;
43+
import com.ibm.fhir.persistence.jdbc.util.CalendarHelper;
4344
import com.ibm.fhir.persistence.jdbc.util.CanonicalSupport;
4445
import com.ibm.fhir.schema.control.FhirSchemaConstants;
4546
import com.ibm.fhir.search.util.ReferenceValue;
@@ -415,6 +416,7 @@ public void visit(DateParmVal param) throws FHIRPersistenceException {
415416
}
416417

417418
private void setDateParms(PreparedStatement insert, int parameterNameId, Timestamp dateStart, Timestamp dateEnd) throws SQLException {
419+
final Calendar UTC = CalendarHelper.getCalendarForUTC();
418420
insert.setInt(1, parameterNameId);
419421
insert.setTimestamp(2, dateStart, UTC);
420422
insert.setTimestamp(3, dateEnd, UTC);

fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ResourceDAOImpl.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import static com.ibm.fhir.persistence.jdbc.JDBCConstants.END;
1010
import static com.ibm.fhir.persistence.jdbc.JDBCConstants.THEN;
11-
import static com.ibm.fhir.persistence.jdbc.JDBCConstants.UTC;
1211
import static com.ibm.fhir.persistence.jdbc.JDBCConstants.WHEN;
1312

1413
import java.sql.CallableStatement;
@@ -48,6 +47,7 @@
4847
import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDataAccessException;
4948
import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceFKVException;
5049
import com.ibm.fhir.persistence.jdbc.impl.ParameterTransactionDataImpl;
50+
import com.ibm.fhir.persistence.jdbc.util.CalendarHelper;
5151
import com.ibm.fhir.persistence.jdbc.util.ResourceTypesCache;
5252
import com.ibm.fhir.persistence.jdbc.util.ResourceTypesCacheUpdater;
5353
import com.ibm.fhir.persistence.jdbc.util.SqlQueryData;
@@ -262,7 +262,7 @@ protected Resource createDTO(ResultSet resultSet) throws FHIRPersistenceDataAcce
262262
}
263263
resource.setId(resultSet.getLong(IDX_RESOURCE_ID));
264264
resource.setLogicalResourceId(resultSet.getLong(IDX_LOGICAL_RESOURCE_ID));
265-
resource.setLastUpdated(resultSet.getTimestamp(IDX_LAST_UPDATED));
265+
resource.setLastUpdated(resultSet.getTimestamp(IDX_LAST_UPDATED, CalendarHelper.getCalendarForUTC()));
266266
resource.setLogicalId(resultSet.getString(IDX_LOGICAL_ID));
267267
resource.setVersionId(resultSet.getInt(IDX_VERSION_ID));
268268
resource.setDeleted(resultSet.getString(IDX_IS_DELETED).equals("Y") ? true : false);
@@ -466,7 +466,7 @@ public List<Long> searchForIds(SqlQueryData queryData) throws FHIRPersistenceDat
466466
for (int i = 0; i < queryData.getBindVariables().size(); i++) {
467467
Object object = queryData.getBindVariables().get(i);
468468
if (object instanceof Timestamp) {
469-
stmt.setTimestamp(i + 1, (Timestamp) object, JDBCConstants.UTC);
469+
stmt.setTimestamp(i + 1, (Timestamp) object, CalendarHelper.getCalendarForUTC());
470470
} else {
471471
stmt.setObject(i + 1, object);
472472
}
@@ -582,7 +582,7 @@ public Resource insert(Resource resource, List<ExtractedParameterValue> paramete
582582
}
583583

584584
lastUpdated = resource.getLastUpdated();
585-
stmt.setTimestamp(4, lastUpdated, UTC);
585+
stmt.setTimestamp(4, lastUpdated, CalendarHelper.getCalendarForUTC());
586586
stmt.setString(5, resource.isDeleted() ? "Y": "N");
587587
stmt.setInt(6, resource.getVersionId());
588588
stmt.setString(7, parameterHashB64);

fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/RetrieveIndexDAO.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,13 @@
2222
import com.ibm.fhir.database.utils.api.IDatabaseTranslator;
2323
import com.ibm.fhir.persistence.exception.FHIRPersistenceException;
2424
import com.ibm.fhir.persistence.jdbc.FHIRPersistenceJDBCCache;
25+
import com.ibm.fhir.persistence.jdbc.util.CalendarHelper;
2526

2627
/**
2728
* Simple DAO to retrieve index IDs (i.e. logical resource IDs) from the LOGICAL_RESOURCES table.
2829
*/
2930
public class RetrieveIndexDAO {
3031
private static final Logger logger = Logger.getLogger(RetrieveIndexDAO.class.getName());
31-
private static final Calendar UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
32-
3332
private final IDatabaseTranslator translator;
3433
private final String schemaName;
3534
private final String resourceTypeName;
@@ -109,7 +108,7 @@ public List<Long> run(Connection c) throws FHIRPersistenceException {
109108
ps.setString(i++, resourceTypeName);
110109
}
111110
if (notModifiedAfter != null) {
112-
ps.setTimestamp(i++, Timestamp.from(notModifiedAfter), UTC_CALENDAR);
111+
ps.setTimestamp(i++, Timestamp.from(notModifiedAfter), CalendarHelper.getCalendarForUTC());
113112
}
114113
if (afterLogicalResourceId != null) {
115114
ps.setLong(i++, afterLogicalResourceId);

fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/derby/DerbyResourceDAO.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@
66

77
package com.ibm.fhir.persistence.jdbc.derby;
88

9-
import static com.ibm.fhir.persistence.jdbc.JDBCConstants.UTC;
10-
119
import java.io.InputStream;
1210
import java.sql.Connection;
1311
import java.sql.PreparedStatement;
1412
import java.sql.ResultSet;
1513
import java.sql.SQLException;
1614
import java.sql.SQLIntegrityConstraintViolationException;
1715
import java.sql.Timestamp;
16+
import java.util.Calendar;
1817
import java.util.List;
1918
import java.util.UUID;
2019
import java.util.logging.Level;
@@ -40,6 +39,7 @@
4039
import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDataAccessException;
4140
import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceFKVException;
4241
import com.ibm.fhir.persistence.jdbc.impl.ParameterTransactionDataImpl;
42+
import com.ibm.fhir.persistence.jdbc.util.CalendarHelper;
4343
import com.ibm.fhir.persistence.jdbc.util.ParameterTableSupport;
4444
import com.ibm.fhir.persistence.jdbc.util.ResourceTypesCache;
4545

@@ -207,6 +207,7 @@ public Resource insert(Resource resource, List<ExtractedParameterValue> paramet
207207
public long storeResource(String tablePrefix, List<ExtractedParameterValue> parameters, String p_logical_id, InputStream p_payload, Timestamp p_last_updated, boolean p_is_deleted,
208208
String p_source_key, Integer p_version, String p_parameterHashB64, Connection conn, ParameterDAO parameterDao) throws Exception {
209209

210+
final Calendar UTC = CalendarHelper.getCalendarForUTC();
210211
final String METHODNAME = "storeResource() for " + tablePrefix + " resource";
211212
logger.entering(CLASSNAME, METHODNAME);
212213

0 commit comments

Comments
 (0)