diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index 325fcf30ae..9fbfa7c2b2 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -50,6 +50,7 @@ Smack Extensions and currently supported XEPs of smack-extensions | Advanced Message Processing | [XEP-0079](http://xmpp.org/extensions/xep-0079.html) | Enables entities to request, and servers to perform, advanced processing of XMPP message stanzas. | | User Location | [XEP-0080](http://xmpp.org/extensions/xep-0080.html) | Enabled communicating information about the current geographical or physical location of an entity. | | XMPP Date Time Profiles | [XEP-0082](http://xmpp.org/extensions/xep-0082.html) | Standardization of Date and Time representation in XMPP. | +| User Avatar | [XEP-0084](http://xmpp.org/extensions/xep-0084.html) | Allows to exchange user avatars, which are small images or icons associated with human users. | | Chat State Notifications | [XEP-0085](http://xmpp.org/extensions/xep-0085.html) | Communicating the status of a user in a chat session. | | [Time Exchange](time.md) | [XEP-0090](http://xmpp.org/extensions/xep-0090.html) | Allows local time information to be shared between users. | | Software Version | [XEP-0092](http://xmpp.org/extensions/xep-0092.html) | Retrieve and announce the software application of an XMPP entity. | diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/MetadataInfo.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/MetadataInfo.java new file mode 100644 index 0000000000..cf153ea68a --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/MetadataInfo.java @@ -0,0 +1,108 @@ +/** + * + * Copyright 2017 Fernando Ramirez + * + * 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.avatar; + +/** + * User Avatar metadata info model class. + * + * @author Fernando Ramirez + * @see XEP-0084: User + * Avatar + */ +public class MetadataInfo { + + private final String id; + private final String url; + private final long bytes; + private final String type; + private final int height; + private final int width; + + /** + * MetadataInfo constructor. + * + * @param id + * @param url + * @param bytes + * @param type + * @param pixelsHeight + * @param pixelsWidth + */ + public MetadataInfo(String id, String url, long bytes, String type, int pixelsHeight, int pixelsWidth) { + this.id = id; + this.url = url; + this.bytes = bytes; + this.type = type; + this.height = pixelsHeight; + this.width = pixelsWidth; + } + + /** + * Get the id. + * + * @return the id + */ + public String getId() { + return id; + } + + /** + * Get the url. + * + * @return the url + */ + public String getUrl() { + return url; + } + + /** + * Get the amount of bytes. + * + * @return the amount of bytes + */ + public long getBytes() { + return bytes; + } + + /** + * Get the type. + * + * @return the type + */ + public String getType() { + return type; + } + + /** + * Get the height in pixels. + * + * @return the height in pixels + */ + public int getHeight() { + return height; + } + + /** + * Get the width in pixels. + * + * @return the width in pixels + */ + public int getWidth() { + return width; + } + +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/MetadataPointer.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/MetadataPointer.java new file mode 100644 index 0000000000..dfffebf720 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/MetadataPointer.java @@ -0,0 +1,62 @@ +/** + * + * Copyright 2017 Fernando Ramirez + * + * 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.avatar; + +import java.util.HashMap; + +/** + * User Avatar metadata pointer model class. + * + * @author Fernando Ramirez + * @see XEP-0084: User + * Avatar + */ +public class MetadataPointer { + + private final String namespace; + private final HashMap fields; + + /** + * Metadata Pointer constructor. + * + * @param namespace + * @param fields + */ + public MetadataPointer(String namespace, HashMap fields) { + this.namespace = namespace; + this.fields = fields; + } + + /** + * Get the namespace. + * + * @return the namespace + */ + public String getNamespace() { + return namespace; + } + + /** + * Get the fields. + * + * @return the fields + */ + public HashMap getFields() { + return fields; + } + +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/UserAvatarManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/UserAvatarManager.java new file mode 100644 index 0000000000..27db87813a --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/UserAvatarManager.java @@ -0,0 +1,312 @@ +/** + * + * Copyright 2017 Fernando Ramirez + * + * 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.avatar; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +import org.jivesoftware.smack.ConnectionCreationListener; +import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPConnectionRegistry; +import org.jivesoftware.smack.SmackException.NoResponseException; +import org.jivesoftware.smack.SmackException.NotConnectedException; +import org.jivesoftware.smack.XMPPException.XMPPErrorException; +import org.jivesoftware.smack.util.SHA1; +import org.jivesoftware.smack.util.stringencoder.Base64; +import org.jivesoftware.smackx.avatar.element.DataExtension; +import org.jivesoftware.smackx.avatar.element.MetadataExtension; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.disco.packet.DiscoverItems.Item; +import org.jivesoftware.smackx.pubsub.LeafNode; +import org.jivesoftware.smackx.pubsub.PayloadItem; +import org.jivesoftware.smackx.pubsub.PubSubManager; + +/** + * User Avatar manager class. + * + * @author Fernando Ramirez + * @see XEP-0084: User + * Avatar + */ +public final class UserAvatarManager extends Manager { + + public static final String DATA_NAMESPACE = "urn:xmpp:avatar:data"; + public static final String METADATA_NAMESPACE = "urn:xmpp:avatar:metadata"; + + static { + XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { + @Override + public void connectionCreated(XMPPConnection connection) { + getInstanceFor(connection); + } + }); + } + + private static final Map INSTANCES = new WeakHashMap<>(); + + /** + * Get the singleton instance of UserAvatarManager. + * + * @param connection + * @return the instance of UserAvatarManager + */ + public static synchronized UserAvatarManager getInstanceFor(XMPPConnection connection) { + UserAvatarManager userAvatarManager = INSTANCES.get(connection); + + if (userAvatarManager == null) { + userAvatarManager = new UserAvatarManager(connection); + INSTANCES.put(connection, userAvatarManager); + } + + return userAvatarManager; + } + + private UserAvatarManager(XMPPConnection connection) { + super(connection); + } + + /** + * Returns true if User Avatar is supported by the server. + * + * @return true if User Avatar is supported by the server. + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + */ + public boolean isSupportedByServer() + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + return findNodeItem(DATA_NAMESPACE) && findNodeItem(METADATA_NAMESPACE); + } + + private boolean findNodeItem(String nodeName) + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection()); + List items = serviceDiscoveryManager.discoverItems(connection().getUser().asBareJid(), nodeName) + .getItems(); + return items != null && items.size() > 0; + } + + /** + * Get the data node. + * + * @return the data node + * @throws NoResponseException + * @throws NotConnectedException + * @throws InterruptedException + * @throws XMPPErrorException + */ + public LeafNode getDataNode() + throws NoResponseException, NotConnectedException, InterruptedException, XMPPErrorException { + return PubSubManager.getInstance(connection()).getOrCreateLeafNode(DATA_NAMESPACE); + } + + /** + * Get the metadata node. + * + * @return the metadata node + * @throws NoResponseException + * @throws NotConnectedException + * @throws InterruptedException + * @throws XMPPErrorException + */ + public LeafNode getMetadataNode() + throws NoResponseException, NotConnectedException, InterruptedException, XMPPErrorException { + return PubSubManager.getInstance(connection()).getOrCreateLeafNode(METADATA_NAMESPACE); + } + + /** + * Publish avatar data. + * + * @param data + * @param itemId + * @throws NoResponseException + * @throws NotConnectedException + * @throws XMPPErrorException + * @throws InterruptedException + */ + public void publishAvatarData(byte[] data, String itemId) + throws NoResponseException, NotConnectedException, XMPPErrorException, InterruptedException { + DataExtension dataExtension = new DataExtension(data); + getDataNode().publish(new PayloadItem(itemId, dataExtension)); + } + + /** + * Publish avatar data. + * + * @param data + * @return created item id + * @throws NoResponseException + * @throws NotConnectedException + * @throws XMPPErrorException + * @throws InterruptedException + */ + public String publishAvatarData(byte[] data) + throws NoResponseException, NotConnectedException, XMPPErrorException, InterruptedException { + String itemId = Base64.encodeToString(SHA1.bytes(data)); + publishAvatarData(data, itemId); + return itemId; + } + + /** + * Publish avatar metadata. + * + * @param itemId + * @param info + * @param pointers + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + */ + public void publishAvatarMetadata(String itemId, MetadataInfo info, List pointers) + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + List infos = new ArrayList<>(); + infos.add(info); + publishAvatarMetadata(itemId, infos, pointers); + } + + /** + * Publish avatar metadata. + * + * @param itemId + * @param infos + * @param pointers + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + */ + public void publishAvatarMetadata(String itemId, List infos, List pointers) + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + MetadataExtension metadataExtension = new MetadataExtension(infos, pointers); + getMetadataNode().publish(new PayloadItem(itemId, metadataExtension)); + } + + /** + * Publish HTTP avatar metadata. + * + * @param itemId + * @param id + * @param url + * @param bytes + * @param type + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + */ + public void publishHTTPAvatarMetadata(String itemId, String id, String url, long bytes, String type) + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + MetadataInfo info = new MetadataInfo(id, url, bytes, type, 0, 0); + publishAvatarMetadata(itemId, info, null); + } + + /** + * Publish HTTP avatar metadata with its size in pixels. + * + * @param itemId + * @param id + * @param url + * @param bytes + * @param type + * @param pixelsHeight + * @param pixelsWidth + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + */ + public void publishHTTPAvatarMetadataWithSize(String itemId, String id, String url, long bytes, String type, + int pixelsHeight, int pixelsWidth) + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + MetadataInfo info = new MetadataInfo(id, url, bytes, type, pixelsHeight, pixelsWidth); + publishAvatarMetadata(itemId, info, null); + } + + /** + * Publish avatar metadata. + * + * @param itemId + * @param id + * @param bytes + * @param type + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + */ + public void publishAvatarMetadata(String itemId, String id, long bytes, String type) + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + MetadataInfo info = new MetadataInfo(id, null, bytes, type, 0, 0); + publishAvatarMetadata(itemId, info, null); + } + + /** + * Publish avatar metadata with its size in pixels. + * + * @param itemId + * @param id + * @param bytes + * @param type + * @param pixelsHeight + * @param pixelsWidth + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + */ + public void publishAvatarMetadataWithSize(String itemId, String id, long bytes, String type, int pixelsHeight, + int pixelsWidth) + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + MetadataInfo info = new MetadataInfo(id, null, bytes, type, pixelsHeight, pixelsWidth); + publishAvatarMetadata(itemId, info, null); + } + + /** + * Get last data of an item. + * + * @param itemId + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + */ + public void requestLastData(String itemId) + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + List ids = new ArrayList<>(); + ids.add(itemId); + getDataNode().getItems(ids); + } + + /** + * Disable avatar publishing. + * + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + */ + public void disableAvatarPublishing() + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + getMetadataNode().publish(new PayloadItem(new MetadataExtension(null))); + } + +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/element/DataExtension.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/element/DataExtension.java new file mode 100644 index 0000000000..cc35a96e2c --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/element/DataExtension.java @@ -0,0 +1,84 @@ +/** + * + * Copyright 2017 Fernando Ramirez + * + * 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.avatar.element; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smack.util.stringencoder.Base64; +import org.jivesoftware.smackx.avatar.UserAvatarManager; + +/** + * Data extension element class. + * + * @author Fernando Ramirez + * @see XEP-0084: User + * Avatar + */ +public class DataExtension implements ExtensionElement { + + public static final String ELEMENT = "data"; + public static final String NAMESPACE = UserAvatarManager.DATA_NAMESPACE; + + private final byte[] data; + + /** + * Data Extension constructor. + * + * @param data + */ + public DataExtension(byte[] data) { + this.data = data; + } + + /** + * Get data. + * + * @return a copy of the immutable data + */ + public byte[] getData() { + return data.clone(); + } + + /** + * Get data as String. + * + * @return the data as String + */ + public String getDataAsString() { + return Base64.encodeToString(data); + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public CharSequence toXML() { + XmlStringBuilder xml = new XmlStringBuilder(this); + xml.rightAngleBracket(); + xml.escape(this.getDataAsString()); + xml.closeElement(this); + return xml; + } + +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/element/MetadataExtension.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/element/MetadataExtension.java new file mode 100644 index 0000000000..dcd47c65a0 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/element/MetadataExtension.java @@ -0,0 +1,165 @@ +/** + * + * Copyright 2017 Fernando Ramirez + * + * 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.avatar.element; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.avatar.MetadataInfo; +import org.jivesoftware.smackx.avatar.MetadataPointer; +import org.jivesoftware.smackx.avatar.UserAvatarManager; + +/** + * Metadata extension element class. + * + * @author Fernando Ramirez + * @see XEP-0084: User + * Avatar + */ +public class MetadataExtension implements ExtensionElement { + + public static final String ELEMENT = "metadata"; + public static final String NAMESPACE = UserAvatarManager.METADATA_NAMESPACE; + + private final List infos; + private final List pointers; + + /** + * Metadata Extension constructor. + * + * @param infos + */ + public MetadataExtension(List infos) { + this(infos, null); + } + + /** + * Metadata Extension constructor. + * + * @param infos + * @param pointers + */ + public MetadataExtension(List infos, List pointers) { + this.infos = infos; + this.pointers = pointers; + } + + /** + * Get the info elements list. + * + * @return the info elements list + */ + public List getInfos() { + return Collections.unmodifiableList(infos); + } + + /** + * Get the pointer elements list. + * + * @return the pointer elements list + */ + public List getPointers() { + return (pointers == null) ? null : Collections.unmodifiableList(pointers); + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public CharSequence toXML() { + XmlStringBuilder xml = new XmlStringBuilder(this); + parseInfos(xml); + parsePointers(xml); + closeElement(xml); + return xml; + } + + private void parseInfos(XmlStringBuilder xml) { + if (infos != null) { + xml.rightAngleBracket(); + + for (MetadataInfo info : infos) { + xml.halfOpenElement("info"); + xml.attribute("id", info.getId()); + xml.attribute("bytes", String.valueOf(info.getBytes())); + xml.attribute("type", info.getType()); + xml.optAttribute("url", info.getUrl()); + + if (info.getHeight() > 0) { + xml.attribute("height", info.getHeight()); + } + + if (info.getWidth() > 0) { + xml.attribute("width", info.getWidth()); + } + + xml.closeEmptyElement(); + } + } + } + + private void parsePointers(XmlStringBuilder xml) { + if (pointers != null) { + + for (MetadataPointer pointer : pointers) { + xml.openElement("pointer"); + xml.halfOpenElement("x"); + + String namespace = pointer.getNamespace(); + if (namespace != null) { + xml.xmlnsAttribute(namespace); + } + + xml.rightAngleBracket(); + + HashMap fields = pointer.getFields(); + if (fields != null) { + Iterator> it = fields.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = it.next(); + xml.escapedElement(pair.getKey(), String.valueOf(pair.getValue())); + } + } + + xml.closeElement("x"); + xml.closeElement("pointer"); + } + + } + } + + private void closeElement(XmlStringBuilder xml) { + if (infos != null || pointers != null) { + xml.closeElement(this); + } else { + xml.closeEmptyElement(); + } + } + +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/element/package-info.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/element/package-info.java new file mode 100644 index 0000000000..f6e4ab263e --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/element/package-info.java @@ -0,0 +1,24 @@ +/** + * + * Copyright 2017 Fernando Ramirez + * + * 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. + */ +/** + * User Avatar elements. + * + * @author Fernando Ramirez + * @see XEP-0084: User + * Avatar + */ +package org.jivesoftware.smackx.avatar.element; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/package-info.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/package-info.java new file mode 100644 index 0000000000..b61f2bf409 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/package-info.java @@ -0,0 +1,24 @@ +/** + * + * Copyright 2017 Fernando Ramirez + * + * 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. + */ +/** + * Classes and interfaces of User Avatar. + * + * @author Fernando Ramirez + * @see XEP-0084: User + * Avatar + */ +package org.jivesoftware.smackx.avatar; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/provider/DataProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/provider/DataProvider.java new file mode 100644 index 0000000000..1a257ba8c1 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/provider/DataProvider.java @@ -0,0 +1,39 @@ +/** + * + * Copyright 2017 Fernando Ramirez + * + * 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.avatar.provider; + +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smack.util.stringencoder.Base64; +import org.jivesoftware.smackx.avatar.element.DataExtension; +import org.xmlpull.v1.XmlPullParser; + +/** + * User Avatar data provider class. + * + * @author Fernando Ramirez + * @see XEP-0084: User + * Avatar + */ +public class DataProvider extends ExtensionElementProvider { + + @Override + public DataExtension parse(XmlPullParser parser, int initialDepth) throws Exception { + byte[] data = Base64.decode(parser.nextText()); + return new DataExtension(data); + } + +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/provider/MetadataProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/provider/MetadataProvider.java new file mode 100644 index 0000000000..d987cd2b9d --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/provider/MetadataProvider.java @@ -0,0 +1,139 @@ +/** + * + * Copyright 2017 Fernando Ramirez + * + * 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.avatar.provider; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smackx.avatar.MetadataInfo; +import org.jivesoftware.smackx.avatar.MetadataPointer; +import org.jivesoftware.smackx.avatar.element.MetadataExtension; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +/** + * User Avatar metedata provider class. + * + * @author Fernando Ramirez + * @see XEP-0084: User + * Avatar + */ +public class MetadataProvider extends ExtensionElementProvider { + + @Override + public MetadataExtension parse(XmlPullParser parser, int initialDepth) throws Exception { + List metadataInfos = null; + List pointers = null; + + outerloop: while (true) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG) { + + if (parser.getName().equals("info")) { + if (metadataInfos == null) { + metadataInfos = new ArrayList<>(); + } + + MetadataInfo info = parseInfo(parser); + if (info.getId() != null) { + metadataInfos.add(info); + } + } + + if (parser.getName().equals("pointer")) { + if (pointers == null) { + pointers = new ArrayList<>(); + } + + pointers.add(parsePointer(parser)); + } + + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getDepth() == initialDepth) { + break outerloop; + } + } + } + + return new MetadataExtension(metadataInfos, pointers); + } + + private MetadataInfo parseInfo(XmlPullParser parser) { + String id = null; + String url = null; + long bytes = 0; + String type = null; + int pixelsHeight = 0; + int pixelsWidth = 0; + + id = parser.getAttributeValue("", "id"); + url = parser.getAttributeValue("", "url"); + type = parser.getAttributeValue("", "type"); + + String bytesString = parser.getAttributeValue("", "bytes"); + if (bytesString != null) { + bytes = Long.parseLong(bytesString); + } + + String widthString = parser.getAttributeValue("", "width"); + if (widthString != null) { + pixelsWidth = Integer.parseInt(widthString); + } + + String heightString = parser.getAttributeValue("", "height"); + if (heightString != null) { + pixelsHeight = Integer.parseInt(heightString); + } + + return new MetadataInfo(id, url, bytes, type, pixelsHeight, pixelsWidth); + } + + private MetadataPointer parsePointer(XmlPullParser parser) throws XmlPullParserException, IOException { + int pointerDepth = parser.getDepth(); + String namespace = null; + HashMap fields = null; + + outerloop2: while (true) { + int eventType2 = parser.next(); + + if (eventType2 == XmlPullParser.START_TAG) { + if (parser.getName().equals("x")) { + namespace = parser.getNamespace(); + } else { + if (fields == null) { + fields = new HashMap<>(); + } + + String name = parser.getName(); + Object value = parser.nextText(); + fields.put(name, value); + } + } else if (eventType2 == XmlPullParser.END_TAG) { + if (parser.getDepth() == pointerDepth) { + break outerloop2; + } + } + } + + return new MetadataPointer(namespace, fields); + } + +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/provider/package-info.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/provider/package-info.java new file mode 100644 index 0000000000..6529523cb1 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/avatar/provider/package-info.java @@ -0,0 +1,24 @@ +/** + * + * Copyright 2017 Fernando Ramirez + * + * 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. + */ +/** + * User Avatar providers. + * + * @author Fernando Ramirez + * @see XEP-0084: User + * Avatar + */ +package org.jivesoftware.smackx.avatar.provider; diff --git a/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.providers b/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.providers index 41705f0ca0..ce95f05276 100644 --- a/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.providers +++ b/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.providers @@ -544,4 +544,16 @@ org.jivesoftware.smackx.bob.provider.BoBIQProvider + + + data + urn:xmpp:avatar:data + org.jivesoftware.smackx.avatar.provider.DataProvider + + + metadata + urn:xmpp:avatar:metadata + org.jivesoftware.smackx.avatar.provider.MetadataProvider + + diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/avatar/DataExtensionTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/avatar/DataExtensionTest.java new file mode 100644 index 0000000000..7db73e7c9c --- /dev/null +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/avatar/DataExtensionTest.java @@ -0,0 +1,47 @@ +/** + * + * Copyright 2017 Fernando Ramirez + * + * 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.avatar; + +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smack.util.stringencoder.Base64; +import org.jivesoftware.smackx.avatar.element.DataExtension; +import org.jivesoftware.smackx.avatar.provider.DataProvider; +import org.junit.Assert; +import org.junit.Test; +import org.xmlpull.v1.XmlPullParser; + +public class DataExtensionTest extends SmackTestSuite { + + // @formatter:off + String dataExtensionExample = "" + + "qANQR1DBwU4DX7jmYZnnfe32" + + ""; + // @formatter:on + + @Test + public void checkDataExtensionParse() throws Exception { + byte[] data = Base64.decode("qANQR1DBwU4DX7jmYZnnfe32"); + DataExtension dataExtension = new DataExtension(data); + Assert.assertEquals(dataExtensionExample, dataExtension.toXML().toString()); + + XmlPullParser parser = PacketParserUtils.getParserFor(dataExtensionExample); + DataExtension dataExtensionFromProvider = new DataProvider().parse(parser); + Assert.assertEquals(Base64.encodeToString(data), Base64.encodeToString(dataExtensionFromProvider.getData())); + } + +} diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/avatar/MetadataExtensionTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/avatar/MetadataExtensionTest.java new file mode 100644 index 0000000000..cfeaeb4a82 --- /dev/null +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/avatar/MetadataExtensionTest.java @@ -0,0 +1,213 @@ +/** + * + * Copyright 2017 Fernando Ramirez + * + * 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.avatar; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smackx.avatar.element.MetadataExtension; +import org.jivesoftware.smackx.avatar.provider.MetadataProvider; +import org.junit.Assert; +import org.junit.Test; +import org.xmlpull.v1.XmlPullParser; + +public class MetadataExtensionTest { + + // @formatter:off + String metadataExtensionExample = "" + + "" + + ""; + // @formatter:on + + // @formatter:off + String emptyMetadataExtensionExample = ""; + // @formatter:on + + // @formatter:off + String metadataWithSeveralInfos = "" + + "" + + "" + + "" + + ""; + // @formatter:on + + // @formatter:off + String metadataWithInfoAndPointers = "" + + "" + + "" + + "" + + "Ancapistan" + + "Kropotkin" + + "" + + "" + + "" + + "" + + "hard" + + "2" + + "" + + "" + + ""; + // @formatter:on + + @Test + public void checkMetadataExtensionParse() throws Exception { + String id = "357a8123a30844a3aa99861b6349264ba67a5694"; + String url = "http://avatars.example.org/happy.gif"; + long bytes = 23456; + String type = "image/gif"; + int pixelsHeight = 64; + int pixelsWidth = 128; + + MetadataInfo info = new MetadataInfo(id, url, bytes, type, pixelsHeight, pixelsWidth); + List infos = new ArrayList<>(); + infos.add(info); + + MetadataExtension metadataExtension = new MetadataExtension(infos); + Assert.assertEquals(metadataExtensionExample, metadataExtension.toXML().toString()); + + XmlPullParser parser = PacketParserUtils.getParserFor(metadataExtensionExample); + MetadataExtension metadataExtensionFromProvider = new MetadataProvider().parse(parser); + + Assert.assertEquals(id, metadataExtensionFromProvider.getInfos().get(0).getId()); + Assert.assertEquals(url, metadataExtensionFromProvider.getInfos().get(0).getUrl()); + Assert.assertEquals(bytes, metadataExtensionFromProvider.getInfos().get(0).getBytes()); + Assert.assertEquals(type, metadataExtensionFromProvider.getInfos().get(0).getType()); + Assert.assertEquals(pixelsHeight, metadataExtensionFromProvider.getInfos().get(0).getHeight()); + Assert.assertEquals(pixelsWidth, metadataExtensionFromProvider.getInfos().get(0).getWidth()); + } + + @Test + public void checkEmptyMetadataExtensionParse() throws Exception { + MetadataExtension metadataExtension = new MetadataExtension(null); + Assert.assertEquals(emptyMetadataExtensionExample, metadataExtension.toXML().toString()); + } + + @Test + public void checkSeveralInfosInMetadataExtension() throws Exception { + XmlPullParser parser = PacketParserUtils.getParserFor(metadataWithSeveralInfos); + MetadataExtension metadataExtensionFromProvider = new MetadataProvider().parse(parser); + + MetadataInfo info1 = metadataExtensionFromProvider.getInfos().get(0); + MetadataInfo info2 = metadataExtensionFromProvider.getInfos().get(1); + MetadataInfo info3 = metadataExtensionFromProvider.getInfos().get(2); + + Assert.assertEquals("111f4b3c50d7b0df729d299bc6f8e9ef9066971f", info1.getId()); + Assert.assertNull(info1.getUrl()); + Assert.assertEquals(12345, info1.getBytes()); + Assert.assertEquals("image/png", info1.getType()); + Assert.assertEquals(64, info1.getHeight()); + Assert.assertEquals(64, info1.getWidth()); + + Assert.assertEquals("e279f80c38f99c1e7e53e262b440993b2f7eea57", info2.getId()); + Assert.assertEquals("http://avatars.example.org/happy.png", info2.getUrl()); + Assert.assertEquals(12345, info2.getBytes()); + Assert.assertEquals("image/png", info2.getType()); + Assert.assertEquals(64, info2.getHeight()); + Assert.assertEquals(128, info2.getWidth()); + + Assert.assertEquals("357a8123a30844a3aa99861b6349264ba67a5694", info3.getId()); + Assert.assertEquals("http://avatars.example.org/happy.gif", info3.getUrl()); + Assert.assertEquals(23456, info3.getBytes()); + Assert.assertEquals("image/gif", info3.getType()); + Assert.assertEquals(64, info3.getHeight()); + Assert.assertEquals(64, info3.getWidth()); + } + + @Test + public void checkInfosAndPointersParse() throws Exception { + XmlPullParser parser = PacketParserUtils.getParserFor(metadataWithInfoAndPointers); + MetadataExtension metadataExtensionFromProvider = new MetadataProvider().parse(parser); + + MetadataInfo info = metadataExtensionFromProvider.getInfos().get(0); + Assert.assertEquals("111f4b3c50d7b0df729d299bc6f8e9ef9066971f", info.getId()); + Assert.assertNull(info.getUrl()); + Assert.assertEquals(12345, info.getBytes()); + Assert.assertEquals("image/png", info.getType()); + Assert.assertEquals(64, info.getHeight()); + Assert.assertEquals(64, info.getWidth()); + + MetadataPointer pointer1 = metadataExtensionFromProvider.getPointers().get(0); + HashMap fields1 = pointer1.getFields(); + Assert.assertEquals("http://example.com/virtualworlds", pointer1.getNamespace()); + Assert.assertEquals("Ancapistan", fields1.get("game")); + Assert.assertEquals("Kropotkin", fields1.get("character")); + + MetadataPointer pointer2 = metadataExtensionFromProvider.getPointers().get(1); + HashMap fields2 = pointer2.getFields(); + Assert.assertEquals("http://sample.com/game", pointer2.getNamespace()); + Assert.assertEquals("hard", fields2.get("level")); + Assert.assertEquals("2", fields2.get("players")); + } + + @Test + public void createMetadataExtensionWithInfoAndPointer() { + String id = "111f4b3c50d7b0df729d299bc6f8e9ef9066971f"; + long bytes = 12345; + String type = "image/png"; + int pixelsHeight = 64; + int pixelsWidth = 64; + MetadataInfo info = new MetadataInfo(id, null, bytes, type, pixelsHeight, pixelsWidth); + + HashMap fields1 = new HashMap<>(); + fields1.put("game", "Ancapistan"); + fields1.put("character", "Kropotkin"); + MetadataPointer pointer1 = new MetadataPointer("http://example.com/virtualworlds", fields1); + + HashMap fields2 = new HashMap<>(); + fields2.put("level", "hard"); + fields2.put("players", 2); + MetadataPointer pointer2 = new MetadataPointer("http://sample.com/game", fields2); + + List infos = new ArrayList<>(); + infos.add(info); + + List pointers = new ArrayList<>(); + pointers.add(pointer1); + pointers.add(pointer2); + + MetadataExtension metadataExtension = new MetadataExtension(infos, pointers); + Assert.assertEquals(metadataWithInfoAndPointers, metadataExtension.toXML().toString()); + } + +}