Skip to content

Commit 3248cfa

Browse files
committed
OF-2500: Switch from java.sql.Timestamp to java.time.Instant
Not using the older Java SQL data types but `java.time` ones instead makes life a lot less complex when dealing with time zone oddities. This change requires JDBC 4.2 support in the database driver, and expects the corresponding database columns to be of type `TIMESTAMP WITH TIME ZONE`
1 parent 8fe960e commit 3248cfa

File tree

7 files changed

+82
-77
lines changed

7 files changed

+82
-77
lines changed

documentation/database-guide.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,18 @@ <h2>Data Type Conventions</h2>
4949
</p>
5050
<h3>Dates in Openfire 5.2.0 and later</h3>
5151
<p>
52-
Openfire stores dates as TIMESTAMP values, with fractional seconds parts. To help prevent environmental
52+
Openfire stores dates as TIMESTAMP WITH TIME ZONE values, with fractional seconds parts. To help prevent environmental
5353
configurations from imposing a timezone context to these values, values are to be written to and obtained
54-
from the database using the JDBC <code>setTimestamp()</code> methods with a third argument that explicitly
55-
defines the UTC-calendar, as shown below.
54+
from the database using the JDBC 4.2 <code>getObject()</code> <code>setObject()</code> methods with a Java
55+
representations from <code>java.time</code> (e.g. <code>java.time.Instant</code>).
5656
</p>
5757
<p>To read a timestamp from the database:</p>
5858
<ul style="list-style: none">
59-
<li><code>Date creationDate = rs.getTimestamp("creationDate", Calendar.getInstance(TimeZone.getTimeZone("UTC")));</code></li>
59+
<li><code>Instant creationDate = rs.getObject("creationDate")</code></li>
6060
</ul>
6161
<p>Conversely, to write a timestamp to the database:</p>
6262
<ul style="list-style: none">
63-
<li><code>pstmt.setTimestamp("creationDate", new Timestamp(creationDate.getTime()), Calendar.getInstance(TimeZone.getTimeZone("UTC")));</code></li>
63+
<li><code>pstmt.setObject("creationDate", instant</code></li>
6464
</ul>
6565
<h3>Dates prior to Openfire 5.2.0</h3>
6666
<p>

xmppserver/src/main/java/org/jivesoftware/openfire/OfflineMessageStore.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -184,15 +184,15 @@ public OfflineMessage addMessage(Message message) {
184184
con = DbConnectionManager.getConnection();
185185
pstmt = con.prepareStatement(INSERT_OFFLINE);
186186

187-
Timestamp creationDate = new Timestamp(System.currentTimeMillis());
187+
Instant creationDate = Instant.now();
188188
pstmt.setString(1, username);
189189
pstmt.setLong(2, messageID);
190-
pstmt.setTimestamp(3, creationDate, Calendar.getInstance(TimeZone.getTimeZone("UTC")));
190+
pstmt.setObject(3, creationDate);
191191
pstmt.setInt(4, msgXML.length());
192192
pstmt.setString(5, msgXML);
193193
pstmt.executeUpdate();
194194

195-
offlineMessage = new OfflineMessage(creationDate, message.getElement());
195+
offlineMessage = new OfflineMessage(Date.from(creationDate), message.getElement());
196196
}
197197
catch (Exception e) {
198198
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
@@ -232,18 +232,18 @@ public Collection<OfflineMessage> getMessages(String username, boolean delete) {
232232
rs = pstmt.executeQuery();
233233
while (rs.next()) {
234234
String msgXML = rs.getString(1);
235-
Date creationDate = rs.getTimestamp(2, Calendar.getInstance(TimeZone.getTimeZone("UTC")));
235+
Instant creationDate = rs.getObject(2, Instant.class);
236236
OfflineMessage message;
237237
try {
238-
message = new OfflineMessage(creationDate, SAXReaderUtil.readRootElement(msgXML));
238+
message = new OfflineMessage(Date.from(creationDate), SAXReaderUtil.readRootElement(msgXML));
239239
} catch (ExecutionException e) {
240240
// Try again after removing invalid XML chars (e.g. &#12;)
241241
Matcher matcher = pattern.matcher(msgXML);
242242
if (matcher.find()) {
243243
msgXML = matcher.replaceAll("");
244244
}
245245
try {
246-
message = new OfflineMessage(creationDate, SAXReaderUtil.readRootElement(msgXML));
246+
message = new OfflineMessage(Date.from(creationDate), SAXReaderUtil.readRootElement(msgXML));
247247
} catch (ExecutionException de) {
248248
Log.error("Failed to route packet (offline message): " + msgXML, de);
249249
continue; // skip and process remaining offline messages
@@ -313,7 +313,7 @@ public OfflineMessage getMessage(String username, Date creationDate) {
313313
con = DbConnectionManager.getConnection();
314314
pstmt = con.prepareStatement(LOAD_OFFLINE_MESSAGE);
315315
pstmt.setString(1, username);
316-
pstmt.setTimestamp(2, new Timestamp(creationDate.getTime()), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
316+
pstmt.setObject(2, creationDate.toInstant());
317317
rs = pstmt.executeQuery();
318318
while (rs.next()) {
319319
String msgXML = rs.getString(1);
@@ -380,7 +380,7 @@ public void deleteMessage(String username, Date creationDate) {
380380
con = DbConnectionManager.getConnection();
381381
pstmt = con.prepareStatement(DELETE_OFFLINE_MESSAGE);
382382
pstmt.setString(1, username);
383-
pstmt.setTimestamp(2, new Timestamp(creationDate.getTime()), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
383+
pstmt.setObject(2, creationDate.toInstant());
384384
pstmt.executeUpdate();
385385

386386
// Force a refresh for next call to getSize(username),
@@ -683,7 +683,7 @@ private boolean deleteOldOfflineMessagesFromDB() {
683683
pstmt = con.prepareStatement(DELETE_OFFLINE_MESSAGE_BEFORE);
684684

685685
final Instant pastTime = Instant.now().minus(OFFLINE_AUTOCLEAN_DAYSTOLIVE.getValue());
686-
pstmt.setTimestamp(1, new Timestamp(pastTime.toEpochMilli()), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
686+
pstmt.setObject(1, pastTime);
687687
final int updateCount = pstmt.executeUpdate();
688688
Log.info("Offline message cleaning - Cleaning successful. Removed {} message(s)", updateCount);
689689
return true;

xmppserver/src/main/java/org/jivesoftware/openfire/lockout/DefaultLockOutProvider.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.jivesoftware.openfire.lockout;
1717

1818
import java.sql.*;
19+
import java.time.Instant;
1920
import java.util.Calendar;
2021
import java.util.Date;
2122
import java.util.TimeZone;
@@ -67,9 +68,9 @@ public LockOutFlag getDisabledStatus(String username) {
6768
if (!rs.next()) {
6869
return null;
6970
}
70-
Date startTime = rs.getTimestamp(2, Calendar.getInstance(TimeZone.getTimeZone("UTC")));
71-
Date endTime = rs.getTimestamp(3, Calendar.getInstance(TimeZone.getTimeZone("UTC")));
72-
ret = new LockOutFlag(username, startTime, endTime);
71+
Instant startTime = rs.getObject(2, Instant.class);
72+
Instant endTime = rs.getObject(3, Instant.class);
73+
ret = new LockOutFlag(username, startTime == null ? null : Date.from(startTime), endTime == null ? null : Date.from(endTime));
7374
}
7475
catch (Exception e) {
7576
Log.error("Error loading lockout information from DB", e);
@@ -107,16 +108,16 @@ public void setDisabledStatus(LockOutFlag flag) {
107108
pstmt = con.prepareStatement(ADD_FLAG);
108109
pstmt.setString(1, flag.getUsername());
109110
if (flag.getStartTime() != null) {
110-
pstmt.setTimestamp(2, new Timestamp(flag.getStartTime().getTime()), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
111+
pstmt.setObject(2, flag.getStartTime().toInstant());
111112
}
112113
else {
113-
pstmt.setNull(2, Types.TIMESTAMP);
114+
pstmt.setNull(2, Types.TIMESTAMP_WITH_TIMEZONE);
114115
}
115116
if (flag.getEndTime() != null) {
116-
pstmt.setTimestamp(3, new Timestamp(flag.getEndTime().getTime()), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
117+
pstmt.setObject(3, flag.getEndTime().toInstant());
117118
}
118119
else {
119-
pstmt.setNull(3, Types.TIMESTAMP);
120+
pstmt.setNull(3, Types.TIMESTAMP_WITH_TIMEZONE);
120121
}
121122
pstmt.executeUpdate();
122123
}

xmppserver/src/main/java/org/jivesoftware/openfire/muc/spi/MUCPersistenceManager.java

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import javax.annotation.Nonnull;
3131
import java.math.BigInteger;
3232
import java.sql.*;
33+
import java.time.Instant;
3334
import java.util.Date;
3435
import java.util.*;
3536
import java.util.concurrent.ConcurrentHashMap;
@@ -242,13 +243,13 @@ public static void loadFromDB(MUCRoom room) {
242243
throw new IllegalArgumentException("Room " + room.getName() + " was not found in the database.");
243244
}
244245
room.setID(rs.getLong("roomID"));
245-
room.setCreationDate(rs.getTimestamp("creationDate", Calendar.getInstance(TimeZone.getTimeZone("UTC")) ));
246-
room.setModificationDate(rs.getTimestamp("modificationDate", Calendar.getInstance(TimeZone.getTimeZone("UTC")) ));
246+
room.setCreationDate(Date.from(rs.getObject("creationDate", Instant.class)));
247+
room.setModificationDate(Date.from(rs.getObject("modificationDate", Instant.class)));
247248
room.setNaturalLanguageName(rs.getString("naturalName"));
248249
room.setDescription(rs.getString("description"));
249-
room.setLockedDate(rs.getTimestamp("lockedDate", Calendar.getInstance(TimeZone.getTimeZone("UTC")) ));
250-
if (rs.getTimestamp("emptyDate", Calendar.getInstance(TimeZone.getTimeZone("UTC"))) != null) {
251-
room.setEmptyDate(rs.getTimestamp("emptyDate", Calendar.getInstance(TimeZone.getTimeZone("UTC"))));
250+
room.setLockedDate(Date.from(rs.getObject("lockedDate", Instant.class)));
251+
if (rs.getObject("emptyDate", Instant.class) != null) {
252+
room.setEmptyDate(Date.from(rs.getObject("emptyDate", Instant.class)));
252253
}
253254
else {
254255
room.setEmptyDate(null);
@@ -398,7 +399,7 @@ public static void saveToDB(MUCRoom room) {
398399
con = DbConnectionManager.getConnection();
399400
if (room.wasSavedToDB()) {
400401
pstmt = con.prepareStatement(UPDATE_ROOM);
401-
pstmt.setTimestamp(1, new Timestamp(room.getModificationDate().getTime()), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
402+
pstmt.setObject(1, room.getModificationDate().toInstant());
402403
pstmt.setString(2, room.getNaturalLanguageName());
403404
pstmt.setString(3, room.getDescription());
404405
pstmt.setInt(4, (room.canOccupantsChangeSubject() ? 1 : 0));
@@ -450,18 +451,18 @@ public static void saveToDB(MUCRoom room) {
450451
pstmt = con.prepareStatement(ADD_ROOM);
451452
pstmt.setLong(1, XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatServiceID(room.getMUCService().getServiceName()));
452453
pstmt.setLong(2, room.getID());
453-
pstmt.setTimestamp(3, new Timestamp(room.getCreationDate().getTime()), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
454-
pstmt.setTimestamp(4, new Timestamp(room.getModificationDate().getTime()), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
454+
pstmt.setObject(3, room.getCreationDate().toInstant());
455+
pstmt.setObject(4, room.getModificationDate().toInstant());
455456
pstmt.setString(5, room.getName());
456457
pstmt.setString(6, room.getNaturalLanguageName());
457458
pstmt.setString(7, room.getDescription());
458-
pstmt.setTimestamp(8, new Timestamp(room.getLockedDate().getTime()), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
459+
pstmt.setObject(8, room.getLockedDate().toInstant());
459460
Date emptyDate = room.getEmptyDate();
460461
if (emptyDate == null) {
461-
pstmt.setTimestamp(9, null);
462+
pstmt.setObject(9, null);
462463
}
463464
else {
464-
pstmt.setTimestamp(9, new Timestamp(emptyDate.getTime()), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
465+
pstmt.setObject(9, emptyDate.toInstant());
465466
}
466467
pstmt.setInt(10, (room.canOccupantsChangeSubject() ? 1 : 0));
467468
pstmt.setInt(11, room.getMaxUsers());
@@ -731,7 +732,7 @@ private static Map<Long, MUCRoom> loadRooms(Long serviceID, Date cleanupDate, Mu
731732
{
732733
statement = connection.prepareStatement(RELOAD_ALL_ROOMS_WITH_RECENT_ACTIVITY);
733734
statement.setLong(1, serviceID);
734-
statement.setTimestamp(2, new Timestamp(cleanupDate.getTime()), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
735+
statement.setObject(2, cleanupDate.toInstant());
735736
}
736737
else
737738
{
@@ -744,13 +745,13 @@ private static Map<Long, MUCRoom> loadRooms(Long serviceID, Date cleanupDate, Mu
744745
try {
745746
MUCRoom room = new MUCRoom(chatserver, resultSet.getString("name"));
746747
room.setID(resultSet.getLong("roomID"));
747-
room.setCreationDate(resultSet.getTimestamp("creationDate", Calendar.getInstance(TimeZone.getTimeZone("UTC"))));
748-
room.setModificationDate(resultSet.getTimestamp("modificationDate", Calendar.getInstance(TimeZone.getTimeZone("UTC"))));
748+
room.setCreationDate(Date.from(resultSet.getObject("creationDate", Instant.class)));
749+
room.setModificationDate(Date.from(resultSet.getObject("modificationDate", Instant.class)));
749750
room.setNaturalLanguageName(resultSet.getString("naturalName"));
750751
room.setDescription(resultSet.getString("description"));
751-
room.setLockedDate(resultSet.getTimestamp("lockedDate", Calendar.getInstance(TimeZone.getTimeZone("UTC"))));
752-
if (resultSet.getTimestamp("emptyDate", Calendar.getInstance(TimeZone.getTimeZone("UTC"))) != null) {
753-
room.setEmptyDate(resultSet.getTimestamp("emptyDate", Calendar.getInstance(TimeZone.getTimeZone("UTC"))));
752+
room.setLockedDate(Date.from(resultSet.getObject("lockedDate", Instant.class)));
753+
if (resultSet.getObject("emptyDate", Instant.class) != null) {
754+
room.setEmptyDate(Date.from(resultSet.getObject("emptyDate", Instant.class)));
754755
}
755756
else {
756757
room.setEmptyDate(null);
@@ -879,7 +880,7 @@ public static void loadHistory(@Nonnull final MUCRoom room, final int maxNumber)
879880
from = System.currentTimeMillis() - (BigInteger.valueOf(86400000).multiply(BigInteger.valueOf(reloadLimitDays))).longValue();
880881
}
881882

882-
pstmt.setTimestamp(1, new Timestamp(from), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
883+
pstmt.setObject(1, Instant.ofEpochMilli(from));
883884
pstmt.setLong(2, room.getID());
884885
rs = pstmt.executeQuery();
885886

@@ -899,11 +900,11 @@ public static void loadHistory(@Nonnull final MUCRoom room, final int maxNumber)
899900
while (rs.next()) {
900901
String senderJID = rs.getString("sender");
901902
String nickname = rs.getString("nickname");
902-
Date sentDate = rs.getTimestamp("logTime", Calendar.getInstance(TimeZone.getTimeZone("UTC")));
903+
Instant sentDate = rs.getObject("logTime", Instant.class);
903904
String subject = rs.getString("subject");
904905
String body = rs.getString("body");
905906
String stanza = rs.getString("stanza");
906-
oldMessages.add(room.getRoomHistory().parseHistoricMessage(senderJID, nickname, sentDate, subject, body, stanza));
907+
oldMessages.add(room.getRoomHistory().parseHistoricMessage(senderJID, nickname, Date.from(sentDate), subject, body, stanza));
907908
}
908909

909910
if (!oldMessages.isEmpty()) {
@@ -942,7 +943,7 @@ private static void loadHistory(Long serviceID, Map<Long, MUCRoom> rooms) throws
942943
from = System.currentTimeMillis() - (BigInteger.valueOf(86400000).multiply(BigInteger.valueOf(reloadLimitDays))).longValue();
943944
}
944945
statement.setLong(1, serviceID);
945-
statement.setTimestamp(2, new Timestamp(from), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
946+
statement.setObject(2, Instant.ofEpochMilli(from));
946947
resultSet = statement.executeQuery();
947948

948949
while (resultSet.next()) {
@@ -954,11 +955,11 @@ private static void loadHistory(Long serviceID, Map<Long, MUCRoom> rooms) throws
954955
}
955956
String senderJID = resultSet.getString("sender");
956957
String nickname = resultSet.getString("nickname");
957-
Date sentDate = resultSet.getTimestamp("logTime", Calendar.getInstance(TimeZone.getTimeZone("UTC")));
958+
Instant sentDate = resultSet.getObject("logTime", Instant.class);
958959
String subject = resultSet.getString("subject");
959960
String body = resultSet.getString("body");
960961
String stanza = resultSet.getString("stanza");
961-
final Message message = room.getRoomHistory().parseHistoricMessage(senderJID, nickname, sentDate, subject, body, stanza);
962+
final Message message = room.getRoomHistory().parseHistoricMessage(senderJID, nickname, Date.from(sentDate), subject, body, stanza);
962963
room.getRoomHistory().addOldMessages(message);
963964
} catch (SQLException e) {
964965
Log.warn("A database exception prevented the history for one particular MUC room to be loaded from the database.", e);
@@ -1125,7 +1126,7 @@ public static void updateRoomLock(MUCRoom room) {
11251126
try {
11261127
con = DbConnectionManager.getConnection();
11271128
pstmt = con.prepareStatement(UPDATE_LOCK);
1128-
pstmt.setTimestamp(1, new Timestamp(room.getLockedDate().getTime()), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
1129+
pstmt.setObject(1, room.getLockedDate().toInstant());
11291130
pstmt.setLong(2, room.getID());
11301131
pstmt.executeUpdate();
11311132
}
@@ -1154,10 +1155,10 @@ public static void updateRoomEmptyDate(MUCRoom room) {
11541155
pstmt = con.prepareStatement(UPDATE_EMPTYDATE);
11551156
Date emptyDate = room.getEmptyDate();
11561157
if (emptyDate == null) {
1157-
pstmt.setTimestamp(1, null);
1158+
pstmt.setObject(1, null);
11581159
}
11591160
else {
1160-
pstmt.setTimestamp(1, new Timestamp(emptyDate.getTime()), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
1161+
pstmt.setObject(1, emptyDate.toInstant());
11611162
}
11621163
pstmt.setLong(2, room.getID());
11631164
pstmt.executeUpdate();
@@ -1428,7 +1429,7 @@ public static boolean saveConversationLogBatch(List<ConversationLogEntry> batch)
14281429
pstmt.setLong(2, entry.getMessageID());
14291430
pstmt.setString(3, entry.getSender().toString());
14301431
pstmt.setString(4, entry.getNickname());
1431-
pstmt.setTimestamp(5, new Timestamp(entry.getDate().getTime()), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
1432+
pstmt.setObject(5, entry.getDate().toInstant());
14321433
pstmt.setString(6, entry.getSubject());
14331434
pstmt.setString(7, entry.getBody());
14341435
pstmt.setString(8, entry.getStanza());

0 commit comments

Comments
 (0)