1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2025-09-11 03:09:46 +02:00

Rework XMPP Date/Time related code

- Fix "packet.Time is not thread-safe" (SMACK-543)
- Update packet.Time to XEP-0202

Add SDM.supportsFeature(), since this is a pattern that repeats over and
over again in Smack. Also add abstract Manager class, that takes care of
the weak reference to Connection, as this is also a repeating pattern in
Smack.
This commit is contained in:
Florian Schmaus 2014-03-03 09:44:32 +01:00
parent 768700b301
commit 585e20e93e
21 changed files with 904 additions and 678 deletions

View file

@ -18,7 +18,7 @@ package org.jivesoftware.smackx.delay.packet;
import java.util.Date;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmppDateTime;
/**
* A decorator for the {@link DelayInformation} class to transparently support
@ -92,7 +92,7 @@ public class DelayInfo extends DelayInformation
buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
"\"");
buf.append(" stamp=\"");
buf.append(StringUtils.formatXEP0082Date(getStamp()));
buf.append(XmppDateTime.formatXEP0082Date(getStamp()));
buf.append("\"");
if (getFrom() != null && getFrom().length() > 0) {
buf.append(" from=\"").append(getFrom()).append("\"");

View file

@ -21,7 +21,7 @@ import java.util.Date;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmppDateTime;
import org.jivesoftware.smackx.delay.packet.DelayInformation;
import org.xmlpull.v1.XmlPullParser;
@ -38,7 +38,7 @@ public class DelayInformationProvider implements PacketExtensionProvider {
Date stamp = null;
try {
stamp = StringUtils.parseDate(stampString);
stamp = XmppDateTime.parseDate(stampString);
}
catch (ParseException parseExc) {
/*

View file

@ -645,6 +645,19 @@ public class ServiceDiscoveryManager {
connection.createPacketCollectorAndSend(discoverItems).nextResultOrThrow();
}
/**
* Queries the remote jid for it's features and returns true if the given feature is found.
*
* @param jid
* @param feature
* @return
* @throws XMPPException
*/
public boolean supportsFeature(String jid, String feature) throws XMPPException {
DiscoverInfo result = discoverInfo(jid);
return result.containsFeature(feature);
}
/**
* Entity Capabilities
*/

View file

@ -23,7 +23,7 @@ import java.util.Date;
import java.util.Iterator;
import java.util.UnknownFormatConversionException;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmppDateTime;
import org.jivesoftware.smackx.xdata.Form;
import org.jivesoftware.smackx.xdata.FormField;
import org.jivesoftware.smackx.xdata.packet.DataForm;
@ -128,7 +128,7 @@ public class SubscribeForm extends Form
String dateTime = getFieldValue(SubscribeOptionFields.expire);
try
{
return StringUtils.parseDate(dateTime);
return XmppDateTime.parseDate(dateTime);
}
catch (ParseException e)
{
@ -146,7 +146,7 @@ public class SubscribeForm extends Form
public void setExpiry(Date expire)
{
addField(SubscribeOptionFields.expire, FormField.TYPE_TEXT_SINGLE);
setAnswer(SubscribeOptionFields.expire.getFieldName(), StringUtils.formatXEP0082Date(expire));
setAnswer(SubscribeOptionFields.expire.getFieldName(), XmppDateTime.formatXEP0082Date(expire));
}
/**

View file

@ -21,6 +21,7 @@ import java.util.Date;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmppDateTime;
import org.jivesoftware.smackx.xdata.packet.DataForm;
/**
@ -345,7 +346,7 @@ public class StreamInitiation extends IQ {
}
if (getDate() != null) {
buffer.append("date=\"").append(StringUtils.formatXEP0082Date(date)).append("\" ");
buffer.append("date=\"").append(XmppDateTime.formatXEP0082Date(date)).append("\" ");
}
if (getHash() != null) {

View file

@ -23,7 +23,7 @@ import java.util.logging.Logger;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmppDateTime;
import org.jivesoftware.smackx.si.packet.StreamInitiation;
import org.jivesoftware.smackx.si.packet.StreamInitiation.File;
import org.jivesoftware.smackx.xdata.packet.DataForm;
@ -98,7 +98,7 @@ public class StreamInitiationProvider implements IQProvider {
Date fileDate = new Date();
if (date != null) {
try {
fileDate = StringUtils.parseDate(date);
fileDate = XmppDateTime.parseDate(date);
} catch (ParseException e) {
// couldn't parse date, use current date-time
}

View file

@ -0,0 +1,111 @@
/**
*
* Copyright 2014 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.time;
import java.util.Map;
import java.util.WeakHashMap;
import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.IQTypeFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ.Type;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.time.packet.Time;
public class EntityTimeManager extends Manager {
private static final Map<Connection, EntityTimeManager> INSTANCES = new WeakHashMap<Connection, EntityTimeManager>();
private static final PacketFilter TIME_PACKET_FILTER = new AndFilter(new PacketTypeFilter(
Time.class), new IQTypeFilter(Type.GET));
private static boolean autoEnable = true;
static {
Connection.addConnectionCreationListener(new ConnectionCreationListener() {
public void connectionCreated(Connection connection) {
getInstanceFor(connection);
}
});
}
public static void setAutoEnable(boolean autoEnable) {
EntityTimeManager.autoEnable = autoEnable;
}
public synchronized static EntityTimeManager getInstanceFor(Connection connection) {
EntityTimeManager entityTimeManager = INSTANCES.get(connection);
if (entityTimeManager == null) {
entityTimeManager = new EntityTimeManager(connection);
}
return entityTimeManager;
}
private boolean enabled = false;
private EntityTimeManager(Connection connection) {
super(connection);
INSTANCES.put(connection, this);
if (autoEnable)
enable();
connection.addPacketListener(new PacketListener() {
@Override
public void processPacket(Packet packet) {
if (!enabled)
return;
connection().sendPacket(Time.createResponse(packet));
}
}, TIME_PACKET_FILTER);
}
public synchronized void enable() {
if (enabled)
return;
ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection());
sdm.addFeature(Time.NAMESPACE);
enabled = true;
}
public synchronized void disable() {
if (!enabled)
return;
ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection());
sdm.removeFeature(Time.NAMESPACE);
enabled = false;
}
public boolean isTimeSupported(String jid) throws XMPPException {
return ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(jid, Time.NAMESPACE);
}
public Time getTime(String jid) throws XMPPException {
if (!isTimeSupported(jid))
return null;
Time request = new Time();
Time response = (Time) connection().createPacketCollectorAndSend(request).nextResultOrThrow();
return response;
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software.
* Copyright 2003-2007 Jive Software, 2014 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,16 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.time.packet;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.util.XmppDateTime;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -31,49 +29,21 @@ import java.util.logging.Logger;
* A Time IQ packet, which is used by XMPP clients to exchange their respective local
* times. Clients that wish to fully support the entitity time protocol should register
* a PacketListener for incoming time requests that then respond with the local time.
* This class can be used to request the time from other clients, such as in the
* following code snippet:
*
* <pre>
* // Request the time from a remote user.
* Time timeRequest = new Time();
* timeRequest.setType(IQ.Type.GET);
* timeRequest.setTo(someUser@example.com/resource);
*
* // Create a packet collector to listen for a response.
* PacketCollector collector = con.createPacketCollector(
* new PacketIDFilter(timeRequest.getPacketID()));
*
* con.sendPacket(timeRequest);
*
* // Wait up to 5 seconds for a result.
* IQ result = (IQ)collector.nextResult(5000);
* if (result != null && result.getType() == IQ.Type.RESULT) {
* Time timeResult = (Time)result;
* // Do something with result...
* }</pre><p>
*
* Warning: this is an non-standard protocol documented by
* <a href="http://www.xmpp.org/extensions/xep-0090.html">XEP-0090</a>. Because this is a
* non-standard protocol, it is subject to change.
*
* @author Matt Tucker
* @see http://www.xmpp.org/extensions/xep-0202.html
* @author Florian Schmaus
*/
public class Time extends IQ {
public static final String NAMESPACE = "urn:xmpp:time";
public static final String ELEMENT = "time";
private static final Logger LOGGER = Logger.getLogger(Time.class.getName());
private static SimpleDateFormat utcFormat = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
private static DateFormat displayFormat = DateFormat.getDateTimeInstance();
private String utc = null;
private String tz = null;
private String display = null;
private String utc;
private String tzo;
/**
* Creates a new Time instance with empty values for all fields.
*/
public Time() {
setType(Type.GET);
}
/**
@ -83,12 +53,9 @@ public class Time extends IQ {
* @param cal the time value.
*/
public Time(Calendar cal) {
TimeZone timeZone = cal.getTimeZone();
tz = cal.getTimeZone().getID();
display = displayFormat.format(cal.getTime());
tzo = XmppDateTime.asString(cal.getTimeZone());
// Convert local time to the UTC time.
utc = utcFormat.format(new Date(
cal.getTimeInMillis() - timeZone.getOffset(cal.getTimeInMillis())));
utc = XmppDateTime.formatXEP0082Date(cal.getTime());
}
/**
@ -102,11 +69,7 @@ public class Time extends IQ {
}
Date date = null;
try {
Calendar cal = Calendar.getInstance();
// Convert the UTC time to local time.
cal.setTime(new Date(utcFormat.parse(utc).getTime() +
cal.getTimeZone().getOffset(cal.getTimeInMillis())));
date = cal.getTime();
date = XmppDateTime.parseDate(utc);
}
catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error getting local time", e);
@ -120,13 +83,10 @@ public class Time extends IQ {
* @param time the current local time.
*/
public void setTime(Date time) {
// Convert local time to UTC time.
utc = utcFormat.format(new Date(
time.getTime() - TimeZone.getDefault().getOffset(time.getTime())));
}
/**
* Returns the time as a UTC formatted String using the format CCYYMMDDThh:mm:ss.
* Returns the time as a UTC formatted String using the format CCYY-MM-DDThh:mm:ssZ.
*
* @return the time as a UTC formatted String.
*/
@ -135,13 +95,12 @@ public class Time extends IQ {
}
/**
* Sets the time using UTC formatted String in the format CCYYMMDDThh:mm:ss.
* Sets the time using UTC formatted String in the format CCYY-MM-DDThh:mm:ssZ.
*
* @param utc the time using a formatted String.
*/
public void setUtc(String utc) {
this.utc = utc;
}
/**
@ -149,50 +108,34 @@ public class Time extends IQ {
*
* @return the time zone.
*/
public String getTz() {
return tz;
public String getTzo() {
return tzo;
}
/**
* Sets the time zone.
* Sets the time zone offset.
*
* @param tz the time zone.
* @param tzo the time zone offset.
*/
public void setTz(String tz) {
this.tz = tz;
public void setTzo(String tzo) {
this.tzo = tzo;
}
/**
* Returns the local (non-utc) time in human-friendly format.
*
* @return the local time in human-friendly format.
*/
public String getDisplay() {
return display;
}
/**
* Sets the local time in human-friendly format.
*
* @param display the local time in human-friendly format.
*/
public void setDisplay(String display) {
this.display = display;
public static Time createResponse(Packet request) {
Time time = new Time(Calendar.getInstance());
time.setType(Type.RESULT);
time.setTo(request.getFrom());
return time;
}
public String getChildElementXML() {
StringBuilder buf = new StringBuilder();
buf.append("<query xmlns=\"jabber:iq:time\">");
buf.append("<" + ELEMENT + " xmlns='" + NAMESPACE + "'>");
if (utc != null) {
buf.append("<utc>").append(utc).append("</utc>");
buf.append("<tzo>").append(tzo).append("</tzo>");
}
if (tz != null) {
buf.append("<tz>").append(tz).append("</tz>");
}
if (display != null) {
buf.append("<display>").append(display).append("</display>");
}
buf.append("</query>");
buf.append("</" + ELEMENT + ">");
return buf.toString();
}
}

View file

@ -1,19 +1,19 @@
<?xml version="1.0"?>
<!-- Providers file for default Smack extensions -->
<smackProviders>
<!-- Private Data Storage -->
<iqProvider>
<elementName>query</elementName>
<namespace>jabber:iq:private</namespace>
<className>org.jivesoftware.smackx.iqprivate.PrivateDataManager$PrivateDataIQProvider</className>
<elementName>query</elementName>
<namespace>jabber:iq:private</namespace>
<className>org.jivesoftware.smackx.iqprivate.PrivateDataManager$PrivateDataIQProvider</className>
</iqProvider>
<!-- Time -->
<iqProvider>
<elementName>query</elementName>
<namespace>jabber:iq:time</namespace>
<className>org.jivesoftware.smackx.time.packet.Time</className>
<elementName>time</elementName>
<namespace>urn:xmpp:time</namespace>
<className>org.jivesoftware.smackx.time.packet.Time</className>
</iqProvider>
<!-- Message Events -->
@ -209,7 +209,7 @@
<namespace>http://jabber.org/protocol/ibb</namespace>
<className>org.jivesoftware.smackx.bytestreams.ibb.provider.CloseIQProvider</className>
</iqProvider>
<extensionProvider>
<elementName>data</elementName>
<namespace>http://jabber.org/protocol/ibb</namespace>
@ -258,7 +258,7 @@
<namespace>http://jabber.org/protocol/commands</namespace>
<className>org.jivesoftware.smackx.commands.provider.AdHocCommandDataProvider$SessionExpiredError</className>
</extensionProvider>
<!-- SHIM -->
<extensionProvider>
<elementName>headers</elementName>
@ -345,14 +345,14 @@
<namespace>http://jabber.org/protocol/pubsub#owner</namespace>
<className>org.jivesoftware.smackx.pubsub.provider.FormNodeProvider</className>
</extensionProvider>
<!-- XEP-0060 pubsub#event -->
<extensionProvider>
<elementName>event</elementName>
<namespace>http://jabber.org/protocol/pubsub#event</namespace>
<className>org.jivesoftware.smackx.pubsub.provider.EventProvider</className>
</extensionProvider>
<extensionProvider>
<elementName>configuration</elementName>
<namespace>http://jabber.org/protocol/pubsub#event</namespace>
@ -364,31 +364,31 @@
<namespace>http://jabber.org/protocol/pubsub#event</namespace>
<className>org.jivesoftware.smackx.pubsub.provider.SimpleNodeProvider</className>
</extensionProvider>
<extensionProvider>
<elementName>options</elementName>
<namespace>http://jabber.org/protocol/pubsub#event</namespace>
<className>org.jivesoftware.smackx.pubsub.provider.FormNodeProvider</className>
</extensionProvider>
<extensionProvider>
<elementName>items</elementName>
<namespace>http://jabber.org/protocol/pubsub#event</namespace>
<className>org.jivesoftware.smackx.pubsub.provider.ItemsProvider</className>
</extensionProvider>
<extensionProvider>
<elementName>item</elementName>
<namespace>http://jabber.org/protocol/pubsub#event</namespace>
<className>org.jivesoftware.smackx.pubsub.provider.ItemProvider</className>
</extensionProvider>
<extensionProvider>
<elementName>retract</elementName>
<namespace>http://jabber.org/protocol/pubsub#event</namespace>
<className>org.jivesoftware.smackx.pubsub.provider.RetractEventProvider</className>
</extensionProvider>
<extensionProvider>
<elementName>purge</elementName>
<namespace>http://jabber.org/protocol/pubsub#event</namespace>
@ -401,7 +401,7 @@
<namespace>http://jabber.org/protocol/nick</namespace>
<className>org.jivesoftware.smackx.nick.packet.Nick$Provider</className>
</extensionProvider>
<!-- Attention -->
<extensionProvider>
<elementName>attention</elementName>

View file

@ -10,5 +10,6 @@
<className>org.jivesoftware.smackx.commands.AdHocCommandManager</className>
<className>org.jivesoftware.smackx.ping.PingManager</className>
<className>org.jivesoftware.smackx.privacy.PrivacyListManager</className>
<className>org.jivesoftware.smackx.time.EntityTimeManager</className>
</startupClasses>
</smack>

View file

@ -0,0 +1,26 @@
/**
*
* Copyright 2014 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx;
public class InitExtensions {
static {
(new ExtensionsProviderInitializer()).initialize();
(new ExtensionsStartupClasses()).initialize();
}
}

View file

@ -27,7 +27,7 @@ import java.util.GregorianCalendar;
import java.util.Properties;
import java.util.TimeZone;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmppDateTime;
import org.jivesoftware.smackx.delay.packet.DelayInfo;
import org.jivesoftware.smackx.delay.packet.DelayInformation;
import org.jivesoftware.smackx.delay.provider.DelayInfoProvider;
@ -218,7 +218,7 @@ public class DelayInformationTest {
.asString(outputProperties);
delayInfo = (DelayInfo) p.parseExtension(getParser(control, "delay"));
Date controlDate = StringUtils.parseXEP0082Date("2008-06-08T09:16:20.0Z");
Date controlDate = XmppDateTime.parseXEP0082Date("2008-06-08T09:16:20.0Z");
assertEquals(controlDate, delayInfo.getStamp());

View file

@ -0,0 +1,98 @@
/**
*
* Copyright 2014 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.time.packet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import org.jivesoftware.smack.DummyConnection;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.test.util.TestUtils;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smackx.InitExtensions;
import org.junit.Test;
public class TimeTest extends InitExtensions {
@Test
public void parseCurrentTimeTest() {
Calendar calendar = Calendar.getInstance();
Time time = new Time(calendar);
Date date = time.getTime();
Date calendarDate = calendar.getTime();
assertEquals(calendarDate, date);
}
@Test
public void negativeTimezoneTest() {
Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(TimeZone.getTimeZone("GMT-830"));
Time time = new Time(calendar);
assertEquals("-8:30", time.getTzo());
}
@Test
public void positiveTimezoneTest() {
Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(TimeZone.getTimeZone("GMT+830"));
Time time = new Time(calendar);
assertEquals("+8:30", time.getTzo());
}
@Test
public void parseTimeWithIntrospectionTest() throws Exception {
DummyConnection connection = new DummyConnection();
// @formatter:off
final String request =
"<iq type='get'"
+ "from='romeo@montague.net/orchard'"
+ "to='juliet@capulet.com/balcony'"
+ "id='time_1'>"
+ "<time xmlns='urn:xmpp:time'/>"
+ "</iq>";
// @formatter:on
IQ iqRequest = PacketParserUtils.parseIQ(TestUtils.getIQParser(request), connection);
assertTrue(iqRequest instanceof Time);
// @formatter:off
final String response =
"<iq type='result'"
+ "from='juliet@capulet.com/balcony'"
+ "to='romeo@montague.net/orchard'"
+ "id='time_1'>"
+ "<time xmlns='urn:xmpp:time'>"
+ "<tzo>-06:00</tzo>"
+ "<utc>2006-12-19T17:58:35Z</utc>"
+ "</time>"
+ "</iq>";
// @formatter:on
IQ iqResponse = PacketParserUtils.parseIQ(TestUtils.getIQParser(response), connection);
assertTrue(iqResponse instanceof Time);
Time time = (Time) iqResponse;
assertEquals("-06:00", time.getTzo());
assertEquals("2006-12-19T17:58:35Z", time.getUtc());
}
}