diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index 50d9dcccc..fdaf103cf 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -68,6 +68,7 @@ Smack Extensions and currently supported XEPs of smack-extensions | Entity Time | [XEP-0202](https://xmpp.org/extensions/xep-0202.html) | n/a | Allows entities to communicate their local time | | Delayed Delivery | [XEP-0203](https://xmpp.org/extensions/xep-0203.html) | n/a | Extension for communicating the fact that an XML stanza has been delivered with a delay. | | XMPP Over BOSH | [XEP-0206](https://xmpp.org/extensions/xep-0206.html) | n/a | Use Bidirectional-streams Over Synchronous HTTP (BOSH) to transport XMPP stanzas. | +| Data Forms Media Element | [XEP-0221](https://xmpp.org/extensions/xep-0221.html) | n/a | Allows to include media data in XEP-0004 data forms. | | Attention | [XEP-0224](https://xmpp.org/extensions/xep-0224.html) | n/a | Getting attention of another user. | | Bits of Binary | [XEP-0231](https://xmpp.org/extensions/xep-0231.html) | n/a | Including or referring to small bits of binary data in an XML stanza. | | Best Practices for Resource Locking | [XEP-0296](https://xmpp.org/extensions/xep-0296.html) | n/a | Specifies best practices to be followed by Jabber/XMPP clients about when to lock into, and unlock away from, resources. | diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/MediaElementManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/MediaElementManager.java new file mode 100644 index 000000000..aa2596227 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/MediaElementManager.java @@ -0,0 +1,27 @@ +/** + * + * Copyright 2019 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.mediaelement; + +import org.jivesoftware.smackx.mediaelement.provider.MediaElementProvider; +import org.jivesoftware.smackx.xdata.provider.FormFieldChildElementProviderManager; + +public class MediaElementManager { + + static { + FormFieldChildElementProviderManager.addFormFieldChildElementProvider(new MediaElementProvider()); + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/element/MediaElement.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/element/MediaElement.java new file mode 100644 index 000000000..2447d1494 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/element/MediaElement.java @@ -0,0 +1,176 @@ +/** + * + * Copyright 2019 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.mediaelement.element; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.xml.namespace.QName; + +import org.jivesoftware.smack.packet.FullyQualifiedElement; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.NumberUtil; +import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.XmlStringBuilder; + +import org.jivesoftware.smackx.xdata.FormField; +import org.jivesoftware.smackx.xdata.FormFieldChildElement; + +public class MediaElement implements FormFieldChildElement { + + public static final String ELEMENT = "media"; + + public static final String NAMESPACE = "urn:xmpp:media-element"; + + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); + + private final int height; + + private final int width; + + private final List uris; + + public MediaElement(Builder builder) { + this.height = builder.height; + this.width = builder.width; + this.uris = Collections.unmodifiableList(builder.uris); + } + + public int getHeight() { + return height; + } + + public int getWidth() { + return width; + } + + public List getUris() { + return uris; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public QName getQName() { + return QNAME; + } + + @Override + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment); + xml.optIntAttribute("height", height) + .optIntAttribute("width", width) + .rightAngleBracket(); + + xml.append(uris, xmlEnvironment); + + xml.closeElement(this); + return null; + } + + public MediaElement from(FormField formField) { + return (MediaElement) formField.getFormFieldChildElement(QNAME); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private int height, width; + + private List uris = new ArrayList<>(); + + public Builder setHeightAndWidth(int height, int width) { + this.height = NumberUtil.requireUShort16(height); + this.width = NumberUtil.requireUShort16(width); + return this; + } + + public Builder addUri(URI uri, String type) { + return addUri(new Uri(uri, type)); + } + + public Builder addUri(Uri uri) { + uris.add(uri); + return this; + } + + public MediaElement build() { + return new MediaElement(this); + } + } + + public static final class Uri implements FullyQualifiedElement { + public static final String ELEMENT = "uri"; + + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); + + private final URI uri; + private final String type; + + public Uri(URI uri, String type) { + this.uri = Objects.requireNonNull(uri); + this.type = StringUtils.requireNotNullNorEmpty(type, "The 'type' argument must not be null or empty"); + } + + public URI getUri() { + return uri; + } + + public String getType() { + return type; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public QName getQName() { + return QNAME; + } + + @Override + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment); + xml.attribute("type", type) + .rightAngleBracket(); + xml.escape(uri.toString()); + xml.closeElement(this); + return xml; + } + + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/element/package-info.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/element/package-info.java new file mode 100644 index 000000000..1c6c172fb --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/element/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2019 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. + */ + +/** + * Element classes for XEP-0221: Data Forms Media Element. + */ +package org.jivesoftware.smackx.mediaelement.element; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/package-info.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/package-info.java new file mode 100644 index 000000000..c0fa98631 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2019 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. + */ + +/** + * Smacks implementation of XEP-0221: Data Forms Media Element. + */ +package org.jivesoftware.smackx.mediaelement; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/provider/MediaElementProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/provider/MediaElementProvider.java new file mode 100644 index 000000000..e5f33b2a2 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/provider/MediaElementProvider.java @@ -0,0 +1,82 @@ +/** + * + * Copyright 2019 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.mediaelement.provider; + +import java.io.IOException; +import java.net.URI; +import java.util.logging.Logger; + +import javax.xml.namespace.QName; + +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.parsing.SmackParsingException.SmackUriSyntaxParsingException; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smack.xml.XmlPullParser; +import org.jivesoftware.smack.xml.XmlPullParserException; + +import org.jivesoftware.smackx.mediaelement.element.MediaElement; +import org.jivesoftware.smackx.xdata.provider.FormFieldChildElementProvider; + +public class MediaElementProvider extends FormFieldChildElementProvider { + + private static final Logger LOGGER = Logger.getLogger(MediaElementProvider.class.getName()); + + @Override + public QName getQName() { + return MediaElement.QNAME; + } + + @Override + public MediaElement parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws IOException, XmlPullParserException, SmackUriSyntaxParsingException { + Integer height = ParserUtils.getIntegerAttribute(parser, "height"); + Integer width = ParserUtils.getIntegerAttribute(parser, "width"); + + MediaElement.Builder mediaElementBuilder = MediaElement.builder(); + if (height != null && width != null) { + mediaElementBuilder.setHeightAndWidth(height, width); + } else if (height != null || width != null) { + LOGGER.warning("Only one of height and width set while parsing media element"); + } + + outerloop: while (true) { + XmlPullParser.TagEvent event = parser.nextTag(); + switch (event) { + case START_ELEMENT: + QName qname = parser.getQName(); + if (qname.equals(MediaElement.Uri.QNAME)) { + MediaElement.Uri uri = parseUri(parser); + mediaElementBuilder.addUri(uri); + } + break; + case END_ELEMENT: + if (parser.getDepth() == initialDepth) { + break outerloop; + } + break; + } + } + + return mediaElementBuilder.build(); + } + + private MediaElement.Uri parseUri(XmlPullParser parser) + throws SmackUriSyntaxParsingException, XmlPullParserException, IOException { + String type = parser.getAttributeValue("type"); + URI uri = ParserUtils.getUriFromNextText(parser); + return new MediaElement.Uri(uri, type); + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/provider/package-info.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/provider/package-info.java new file mode 100644 index 000000000..9b24fd9dc --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mediaelement/provider/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2019 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. + */ + +/** + * Provider classes for XEP-0221: Data Forms Media Element. + */ +package org.jivesoftware.smackx.mediaelement.provider; diff --git a/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml b/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml index fd878518a..d234bb5b3 100644 --- a/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml +++ b/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml @@ -2,6 +2,7 @@ org.jivesoftware.smackx.disco.ServiceDiscoveryManager org.jivesoftware.smackx.xhtmlim.XHTMLManager + org.jivesoftware.smackx.mediaelement.MediaElementManager org.jivesoftware.smackx.muc.MultiUserChatManager org.jivesoftware.smackx.muc.bookmarkautojoin.MucBookmarkAutojoinManager org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/mediaelement/provider/MediaElementProviderTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/mediaelement/provider/MediaElementProviderTest.java new file mode 100644 index 000000000..d9a59ba9c --- /dev/null +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/mediaelement/provider/MediaElementProviderTest.java @@ -0,0 +1,78 @@ +/** + * + * Copyright 2019 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.mediaelement.provider; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.List; + +import org.jivesoftware.smack.parsing.SmackParsingException; +import org.jivesoftware.smack.test.util.SmackTestUtil; +import org.jivesoftware.smack.xml.XmlPullParserException; + +import org.jivesoftware.smackx.mediaelement.element.MediaElement; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +public class MediaElementProviderTest { + + @ParameterizedTest + @EnumSource(SmackTestUtil.XmlPullParserKind.class) + public void simpleMediaElementTest(SmackTestUtil.XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { + final String xml = + "" + + "" + + "http://victim.example.com/challenges/speech.wav?F3A6292C" + + "" + + ""; + + MediaElement mediaElement = SmackTestUtil.parse(xml, MediaElementProvider.class, parserKind); + assertEquals(80, mediaElement.getHeight()); + assertEquals(290, mediaElement.getWidth()); + + List uris = mediaElement.getUris(); + assertEquals(1, uris.size()); + + MediaElement.Uri uri = uris.get(0); + assertEquals("audio/x-wav", uri.getType()); + assertEquals("http://victim.example.com/challenges/speech.wav?F3A6292C", uri.getUri().toString()); + } + + @ParameterizedTest + @EnumSource(SmackTestUtil.XmlPullParserKind.class) + public void parseMediaElementTest(SmackTestUtil.XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { + final String xml = + "" + + "" + + "http://victim.example.com/challenges/speech.wav?F3A6292C" + + "" + + "" + + "cid:sha1+a15a505e360702b79c75a5f67773072ed392f52a@bob.xmpp.org" + + "" + + "" + + "http://victim.example.com/challenges/speech.mp3?F3A6292C" + + "" + + ""; + + MediaElement mediaElement = SmackTestUtil.parse(xml, MediaElementProvider.class, parserKind); + + List uris = mediaElement.getUris(); + assertEquals(3, uris.size()); + } +}