mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-23 23:02:05 +01:00
XEP-0084: User Avatars
Co-authored-by: vanitasvitae <vanitasvitae@fsfe.org>
This commit is contained in:
parent
f61ecb65e7
commit
f5ac8246f6
22 changed files with 1806 additions and 1 deletions
|
@ -56,6 +56,7 @@ Smack Extensions and currently supported XEPs of smack-extensions
|
||||||
| Advanced Message Processing | [XEP-0079](https://xmpp.org/extensions/xep-0079.html) | n/a | Enables entities to request, and servers to perform, advanced processing of XMPP message stanzas. |
|
| Advanced Message Processing | [XEP-0079](https://xmpp.org/extensions/xep-0079.html) | n/a | Enables entities to request, and servers to perform, advanced processing of XMPP message stanzas. |
|
||||||
| User Location | [XEP-0080](https://xmpp.org/extensions/xep-0080.html) | n/a | Enabled communicating information about the current geographical or physical location of an entity. |
|
| User Location | [XEP-0080](https://xmpp.org/extensions/xep-0080.html) | n/a | Enabled communicating information about the current geographical or physical location of an entity. |
|
||||||
| XMPP Date Time Profiles | [XEP-0082](https://xmpp.org/extensions/xep-0082.html) | n/a | Standardization of Date and Time representation in XMPP. |
|
| XMPP Date Time Profiles | [XEP-0082](https://xmpp.org/extensions/xep-0082.html) | n/a | Standardization of Date and Time representation in XMPP. |
|
||||||
|
| User Avatar | [XEP-0084](https://xmpp.org/extensions/xep-0084.html) | 1.1.2 | Allows to exchange user avatars, which are small images or icons associated with human users. |
|
||||||
| Chat State Notifications | [XEP-0085](https://xmpp.org/extensions/xep-0085.html) | n/a | Communicating the status of a user in a chat session. |
|
| Chat State Notifications | [XEP-0085](https://xmpp.org/extensions/xep-0085.html) | n/a | Communicating the status of a user in a chat session. |
|
||||||
| [Time Exchange](time.md) | [XEP-0090](https://xmpp.org/extensions/xep-0090.html) | n/a | Allows local time information to be shared between users. |
|
| [Time Exchange](time.md) | [XEP-0090](https://xmpp.org/extensions/xep-0090.html) | n/a | Allows local time information to be shared between users. |
|
||||||
| Software Version | [XEP-0092](https://xmpp.org/extensions/xep-0092.html) | n/a | Retrieve and announce the software application of an XMPP entity. |
|
| Software Version | [XEP-0092](https://xmpp.org/extensions/xep-0092.html) | n/a | Retrieve and announce the software application of an XMPP entity. |
|
||||||
|
@ -89,7 +90,6 @@ Smack Extensions and currently supported XEPs of smack-extensions
|
||||||
| [Group Chat Invitations](invitation.md) | n/a | n/a | Send invitations to other users to join a group chat room. |
|
| [Group Chat Invitations](invitation.md) | n/a | n/a | Send invitations to other users to join a group chat room. |
|
||||||
| [Jive Properties](properties.md) | n/a | n/a | TODO |
|
| [Jive Properties](properties.md) | n/a | n/a | TODO |
|
||||||
|
|
||||||
|
|
||||||
Experimental Smack Extensions and currently supported XEPs of smack-experimental
|
Experimental Smack Extensions and currently supported XEPs of smack-experimental
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#Sun Sep 01 01:05:38 CEST 2019
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
172
gradlew
vendored
Executable file
172
gradlew
vendored
Executable file
|
@ -0,0 +1,172 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
84
gradlew.bat
vendored
Normal file
84
gradlew.bat
vendored
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
|
@ -19,6 +19,7 @@ package org.jivesoftware.smack.util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -358,6 +359,13 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public XmlStringBuilder optAttribute(String name, URL url) {
|
||||||
|
if (url != null) {
|
||||||
|
attribute(name, url.toExternalForm());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the given attribute if {@code value => 0}.
|
* Add the given attribute if {@code value => 0}.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2019 Paul Schaub
|
||||||
|
*
|
||||||
|
* 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.jxmpp.jid.EntityBareJid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link AvatarMetadataStore} interface defines methods used by the {@link UserAvatarManager} to determine,
|
||||||
|
* whether the client already has a local copy of a published avatar or if the user needs to be informed about the
|
||||||
|
* update in order to download the image.
|
||||||
|
*/
|
||||||
|
public interface AvatarMetadataStore {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine, if the client already has a copy of the avatar with {@code itemId} available or not.
|
||||||
|
*
|
||||||
|
* @param jid {@link EntityBareJid} of the entity that published the avatar.
|
||||||
|
* @param itemId itemId of the avatar
|
||||||
|
*
|
||||||
|
* @return true if the client already has a local copy of the avatar, false otherwise
|
||||||
|
*/
|
||||||
|
boolean hasAvatarAvailable(EntityBareJid jid, String itemId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the tuple (jid, itemId) as available. This means that the client already has a local copy of the avatar
|
||||||
|
* available and wishes not to be notified about this particular avatar anymore.
|
||||||
|
*
|
||||||
|
* @param jid {@link EntityBareJid} of the entity that published the avatar.
|
||||||
|
* @param itemId itemId of the avatar
|
||||||
|
*/
|
||||||
|
void setAvatarAvailable(EntityBareJid jid, String itemId);
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2017 Fernando Ramirez, 2019 Paul Schaub
|
||||||
|
*
|
||||||
|
* 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.net.URL;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.datatypes.UInt16;
|
||||||
|
import org.jivesoftware.smack.datatypes.UInt32;
|
||||||
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User Avatar metadata info model class.
|
||||||
|
*
|
||||||
|
* @author Fernando Ramirez
|
||||||
|
* @author Paul Schaub
|
||||||
|
* @see <a href="http://xmpp.org/extensions/xep-0084.html">XEP-0084: User
|
||||||
|
* Avatar</a>
|
||||||
|
*/
|
||||||
|
public class MetadataInfo {
|
||||||
|
|
||||||
|
public static final int MAX_HEIGHT = 65536;
|
||||||
|
public static final int MAX_WIDTH = 65536;
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
private final URL url;
|
||||||
|
private final UInt32 bytes;
|
||||||
|
private final String type;
|
||||||
|
private final UInt16 height;
|
||||||
|
private final UInt16 width;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MetadataInfo constructor.
|
||||||
|
*
|
||||||
|
* @param id SHA-1 hash of the image data
|
||||||
|
* @param url http(s) url of the image
|
||||||
|
* @param bytes size of the image in bytes
|
||||||
|
* @param type content type of the image
|
||||||
|
* @param pixelsHeight height of the image in pixels
|
||||||
|
* @param pixelsWidth width of the image in pixels
|
||||||
|
*/
|
||||||
|
public MetadataInfo(String id, URL url, long bytes, String type, int pixelsHeight, int pixelsWidth) {
|
||||||
|
this.id = StringUtils.requireNotNullNorEmpty(id, "ID is required.");
|
||||||
|
this.url = url;
|
||||||
|
this.bytes = UInt32.from(bytes);
|
||||||
|
this.type = StringUtils.requireNotNullNorEmpty(type, "Content Type is required.");
|
||||||
|
if (pixelsHeight < 0 || pixelsHeight > MAX_HEIGHT) {
|
||||||
|
throw new IllegalArgumentException("Image height value must be between 0 and 65536.");
|
||||||
|
}
|
||||||
|
if (pixelsWidth < 0 || pixelsWidth > MAX_WIDTH) {
|
||||||
|
throw new IllegalArgumentException("Image width value must be between 0 and 65536.");
|
||||||
|
}
|
||||||
|
this.height = UInt16.from(pixelsHeight);
|
||||||
|
this.width = UInt16.from(pixelsWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id.
|
||||||
|
*
|
||||||
|
* @return the id
|
||||||
|
*/
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the url of the avatar image.
|
||||||
|
*
|
||||||
|
* @return the url
|
||||||
|
*/
|
||||||
|
public URL getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount of bytes.
|
||||||
|
*
|
||||||
|
* @return the amount of bytes
|
||||||
|
*/
|
||||||
|
public UInt32 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 UInt16 getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the width in pixels.
|
||||||
|
*
|
||||||
|
* @return the width in pixels
|
||||||
|
*/
|
||||||
|
public UInt16 getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.Map;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User Avatar metadata pointer model class.
|
||||||
|
* A pointer element is used to point to an avatar which is not published via PubSub or HTTP, but provided by a
|
||||||
|
* third-party service.
|
||||||
|
*
|
||||||
|
* @author Fernando Ramirez
|
||||||
|
* @see <a href="https://xmpp.org/extensions/xep-0084.html">XEP-0084: User Avatar</a>
|
||||||
|
*/
|
||||||
|
public class MetadataPointer {
|
||||||
|
|
||||||
|
private final String namespace;
|
||||||
|
private final Map<String, Object> fields;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata Pointer constructor.
|
||||||
|
*
|
||||||
|
* The following example
|
||||||
|
* <pre>
|
||||||
|
* {@code
|
||||||
|
* <pointer>
|
||||||
|
* <x xmlns='http://example.com/virtualworlds'>
|
||||||
|
* <game>Ancapistan</game>
|
||||||
|
* <character>Kropotkin</character>
|
||||||
|
* </x>
|
||||||
|
* </pointer>
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
* can be created by constructing the object like this:
|
||||||
|
* <pre>
|
||||||
|
* {@code
|
||||||
|
* Map fields = new HashMap<>();
|
||||||
|
* fields.add("game", "Ancapistan");
|
||||||
|
* fields.add("character", "Kropotkin");
|
||||||
|
* MetadataPointer pointer = new MetadataPointer("http://example.com/virtualworlds", fields);
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param namespace namespace of the child element of the metadata pointer.
|
||||||
|
* @param fields fields of the child element as key, value pairs.
|
||||||
|
*/
|
||||||
|
public MetadataPointer(String namespace, Map<String, Object> fields) {
|
||||||
|
this.namespace = StringUtils.requireNotNullNorEmpty(namespace, "Namespace MUST NOT be null, nor empty.");
|
||||||
|
this.fields = fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the namespace of the pointers child element.
|
||||||
|
*
|
||||||
|
* @return the namespace
|
||||||
|
*/
|
||||||
|
public String getNamespace() {
|
||||||
|
return namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the fields of the pointers child element.
|
||||||
|
*
|
||||||
|
* @return the fields
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getFields() {
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,411 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2017 Fernando Ramirez, 2019 Paul Schaub
|
||||||
|
*
|
||||||
|
* 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.io.BufferedInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.Manager;
|
||||||
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
|
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||||
|
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
|
import org.jivesoftware.smack.packet.Message;
|
||||||
|
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.avatar.listener.AvatarListener;
|
||||||
|
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||||
|
import org.jivesoftware.smackx.pep.PepListener;
|
||||||
|
import org.jivesoftware.smackx.pep.PepManager;
|
||||||
|
import org.jivesoftware.smackx.pubsub.EventElement;
|
||||||
|
import org.jivesoftware.smackx.pubsub.ItemsExtension;
|
||||||
|
import org.jivesoftware.smackx.pubsub.LeafNode;
|
||||||
|
import org.jivesoftware.smackx.pubsub.PayloadItem;
|
||||||
|
import org.jivesoftware.smackx.pubsub.PubSubException;
|
||||||
|
import org.jivesoftware.smackx.pubsub.PubSubManager;
|
||||||
|
|
||||||
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User Avatar manager class.
|
||||||
|
*
|
||||||
|
* @author Fernando Ramirez
|
||||||
|
* @author Paul Schaub
|
||||||
|
* @see <a href="http://xmpp.org/extensions/xep-0084.html">XEP-0084: User
|
||||||
|
* Avatar</a>
|
||||||
|
*/
|
||||||
|
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";
|
||||||
|
public static final String FEATURE_METADATA = METADATA_NAMESPACE + "+notify";
|
||||||
|
|
||||||
|
private static final Map<XMPPConnection, UserAvatarManager> INSTANCES = new WeakHashMap<>();
|
||||||
|
|
||||||
|
private final PepManager pepManager;
|
||||||
|
private final ServiceDiscoveryManager serviceDiscoveryManager;
|
||||||
|
|
||||||
|
private AvatarMetadataStore metadataStore;
|
||||||
|
private final Set<AvatarListener> avatarListeners = new HashSet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the singleton instance of UserAvatarManager.
|
||||||
|
*
|
||||||
|
* @param connection {@link XMPPConnection}.
|
||||||
|
* @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);
|
||||||
|
this.pepManager = PepManager.getInstanceFor(connection);
|
||||||
|
this.serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if User Avatar publishing is supported by the server.
|
||||||
|
* In order to support User Avatars the server must have support for XEP-0163: Personal Eventing Protocol (PEP).
|
||||||
|
*
|
||||||
|
* @return true if User Avatar is supported by the server.
|
||||||
|
*
|
||||||
|
* @see <a href="https://xmpp.org/extensions/xep-0163.html">XEP-0163: Personal Eventing Protocol</a>
|
||||||
|
*
|
||||||
|
* @throws NoResponseException
|
||||||
|
* @throws XMPPErrorException
|
||||||
|
* @throws NotConnectedException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public boolean isSupportedByServer()
|
||||||
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||||
|
return pepManager.isSupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Announce support for User Avatars and start receiving avatar updates.
|
||||||
|
*/
|
||||||
|
public void enable() {
|
||||||
|
pepManager.addPepListener(metadataExtensionListener);
|
||||||
|
serviceDiscoveryManager.addFeature(FEATURE_METADATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop receiving avatar updates.
|
||||||
|
*/
|
||||||
|
public void disable() {
|
||||||
|
serviceDiscoveryManager.removeFeature(FEATURE_METADATA);
|
||||||
|
pepManager.addPepListener(metadataExtensionListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an {@link AvatarMetadataStore} which is used to store information about the local availability of avatar
|
||||||
|
* data.
|
||||||
|
* @param metadataStore metadata store
|
||||||
|
*/
|
||||||
|
public void setAvatarMetadataStore(AvatarMetadataStore metadataStore) {
|
||||||
|
this.metadataStore = metadataStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an {@link AvatarListener} in order to be notified about incoming avatar metadata updates.
|
||||||
|
*
|
||||||
|
* @param listener listener
|
||||||
|
* @return true if the set of listeners did not already contain the listener
|
||||||
|
*/
|
||||||
|
public synchronized boolean addAvatarListener(AvatarListener listener) {
|
||||||
|
return avatarListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister an {@link AvatarListener} to stop being notified about incoming avatar metadata updates.
|
||||||
|
*
|
||||||
|
* @param listener listener
|
||||||
|
* @return true if the set of listeners contained the listener
|
||||||
|
*/
|
||||||
|
public synchronized boolean removeAvatarListener(AvatarListener listener) {
|
||||||
|
return avatarListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the data node.
|
||||||
|
* This node contains the avatar image data.
|
||||||
|
*
|
||||||
|
* @return the data node
|
||||||
|
* @throws NoResponseException
|
||||||
|
* @throws NotConnectedException
|
||||||
|
* @throws InterruptedException
|
||||||
|
* @throws XMPPErrorException
|
||||||
|
*/
|
||||||
|
private LeafNode getOrCreateDataNode()
|
||||||
|
throws NoResponseException, NotConnectedException, InterruptedException, XMPPErrorException, PubSubException.NotALeafNodeException {
|
||||||
|
return pepManager.getPepPubSubManager().getOrCreateLeafNode(DATA_NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the metadata node.
|
||||||
|
* This node contains lightweight metadata information about the data in the data node.
|
||||||
|
*
|
||||||
|
* @return the metadata node
|
||||||
|
* @throws NoResponseException
|
||||||
|
* @throws NotConnectedException
|
||||||
|
* @throws InterruptedException
|
||||||
|
* @throws XMPPErrorException
|
||||||
|
*/
|
||||||
|
private LeafNode getOrCreateMetadataNode()
|
||||||
|
throws NoResponseException, NotConnectedException, InterruptedException, XMPPErrorException, PubSubException.NotALeafNodeException {
|
||||||
|
return pepManager.getPepPubSubManager().getOrCreateLeafNode(METADATA_NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish a PNG Avatar and its metadata to PubSub.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* @param height
|
||||||
|
* @param width
|
||||||
|
* @throws XMPPErrorException
|
||||||
|
* @throws PubSubException.NotALeafNodeException
|
||||||
|
* @throws NotConnectedException
|
||||||
|
* @throws InterruptedException
|
||||||
|
* @throws NoResponseException
|
||||||
|
*/
|
||||||
|
public void publishAvatar(byte[] data, int height, int width)
|
||||||
|
throws XMPPErrorException, PubSubException.NotALeafNodeException, NotConnectedException,
|
||||||
|
InterruptedException, NoResponseException {
|
||||||
|
String id = publishAvatarData(data);
|
||||||
|
publishAvatarMetadata(id, data.length, "image/png", height, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish a PNG avatar and its metadata to PubSub.
|
||||||
|
*
|
||||||
|
* @param pngFile PNG File
|
||||||
|
* @param height height of the image
|
||||||
|
* @param width width of the image
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* @throws XMPPErrorException
|
||||||
|
* @throws PubSubException.NotALeafNodeException
|
||||||
|
* @throws NotConnectedException
|
||||||
|
* @throws InterruptedException
|
||||||
|
* @throws NoResponseException
|
||||||
|
*/
|
||||||
|
public void publishAvatar(File pngFile, int height, int width)
|
||||||
|
throws IOException, XMPPErrorException, PubSubException.NotALeafNodeException, NotConnectedException,
|
||||||
|
InterruptedException, NoResponseException {
|
||||||
|
try (ByteArrayOutputStream out = new ByteArrayOutputStream((int) pngFile.length());
|
||||||
|
InputStream in = new BufferedInputStream(new FileInputStream(pngFile))) {
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int read;
|
||||||
|
while ((read = in.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
byte[] bytes = out.toByteArray();
|
||||||
|
publishAvatar(bytes, height, width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] fetchAvatarFromPubSub(EntityBareJid from, MetadataInfo metadataInfo)
|
||||||
|
throws InterruptedException, PubSubException.NotALeafNodeException, NoResponseException,
|
||||||
|
NotConnectedException, XMPPErrorException, PubSubException.NotAPubSubNodeException {
|
||||||
|
LeafNode dataNode = PubSubManager.getInstanceFor(connection(), from)
|
||||||
|
.getLeafNode(DATA_NAMESPACE);
|
||||||
|
|
||||||
|
List<PayloadItem<DataExtension>> dataItems = dataNode.getItems(1, metadataInfo.getId());
|
||||||
|
DataExtension extension = dataItems.get(0).getPayload();
|
||||||
|
if (metadataStore != null) {
|
||||||
|
metadataStore.setAvatarAvailable(from, metadataInfo.getId());
|
||||||
|
}
|
||||||
|
return extension.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String publishAvatarData(byte[] data)
|
||||||
|
throws NoResponseException, NotConnectedException, XMPPErrorException, InterruptedException, PubSubException.NotALeafNodeException {
|
||||||
|
String itemId = Base64.encodeToString(SHA1.bytes(data));
|
||||||
|
publishAvatarData(data, itemId);
|
||||||
|
return itemId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void publishAvatarData(byte[] data, String itemId)
|
||||||
|
throws NoResponseException, NotConnectedException, XMPPErrorException, InterruptedException, PubSubException.NotALeafNodeException {
|
||||||
|
DataExtension dataExtension = new DataExtension(data);
|
||||||
|
getOrCreateDataNode().publish(new PayloadItem<>(itemId, dataExtension));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish metadata about an avatar to the metadata node.
|
||||||
|
*
|
||||||
|
* @param itemId SHA-1 sum of the image of type image/png
|
||||||
|
* @param info info element containing metadata of the file
|
||||||
|
* @param pointers list of metadata pointer elements
|
||||||
|
*
|
||||||
|
* @throws NoResponseException
|
||||||
|
* @throws XMPPErrorException
|
||||||
|
* @throws NotConnectedException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public void publishAvatarMetadata(String itemId, MetadataInfo info, List<MetadataPointer> pointers)
|
||||||
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, PubSubException.NotALeafNodeException {
|
||||||
|
publishAvatarMetadata(itemId, Collections.singletonList(info), pointers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish avatar metadata.
|
||||||
|
*
|
||||||
|
* @param itemId SHA-1 sum of the avatar image representation of type image/png
|
||||||
|
* @param infos list of metadata elements
|
||||||
|
* @param pointers list of pointer elements
|
||||||
|
*
|
||||||
|
* @throws NoResponseException
|
||||||
|
* @throws XMPPErrorException
|
||||||
|
* @throws NotConnectedException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public void publishAvatarMetadata(String itemId, List<MetadataInfo> infos, List<MetadataPointer> pointers)
|
||||||
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, PubSubException.NotALeafNodeException {
|
||||||
|
MetadataExtension metadataExtension = new MetadataExtension(infos, pointers);
|
||||||
|
getOrCreateMetadataNode().publish(new PayloadItem<>(itemId, metadataExtension));
|
||||||
|
|
||||||
|
if (metadataStore == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Mark our own avatar as locally available so that we don't get updates for it
|
||||||
|
metadataStore.setAvatarAvailable(connection().getUser().asEntityBareJidOrThrow(), itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish metadata about an avatar available via HTTP.
|
||||||
|
* This method can be used together with HTTP File Upload as an alternative to PubSub for avatar publishing.
|
||||||
|
*
|
||||||
|
* @param itemId SHA-1 sum of the avatar image file.
|
||||||
|
* @param url HTTP(S) Url of the image file.
|
||||||
|
* @param bytes size of the file in bytes
|
||||||
|
* @param type content type of the file
|
||||||
|
* @param pixelsHeight height of the image file in pixels
|
||||||
|
* @param pixelsWidth width of the image file in pixels
|
||||||
|
*
|
||||||
|
* @throws NoResponseException
|
||||||
|
* @throws XMPPErrorException
|
||||||
|
* @throws NotConnectedException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public void publishHttpAvatarMetadata(String itemId, URL url, long bytes, String type,
|
||||||
|
int pixelsHeight, int pixelsWidth)
|
||||||
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, PubSubException.NotALeafNodeException {
|
||||||
|
MetadataInfo info = new MetadataInfo(itemId, url, bytes, type, pixelsHeight, pixelsWidth);
|
||||||
|
publishAvatarMetadata(itemId, info, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish avatar metadata with its size in pixels.
|
||||||
|
*
|
||||||
|
* @param itemId
|
||||||
|
* @param bytes
|
||||||
|
* @param type
|
||||||
|
* @param pixelsHeight
|
||||||
|
* @param pixelsWidth
|
||||||
|
* @throws NoResponseException
|
||||||
|
* @throws XMPPErrorException
|
||||||
|
* @throws NotConnectedException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public void publishAvatarMetadata(String itemId, long bytes, String type, int pixelsHeight,
|
||||||
|
int pixelsWidth)
|
||||||
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, PubSubException.NotALeafNodeException {
|
||||||
|
MetadataInfo info = new MetadataInfo(itemId, null, bytes, type, pixelsHeight, pixelsWidth);
|
||||||
|
publishAvatarMetadata(itemId, info, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish an empty metadata element to disable avatar publishing.
|
||||||
|
*
|
||||||
|
* @see <a href="https://xmpp.org/extensions/xep-0084.html#proto-meta">§4.2 Metadata Element</a>
|
||||||
|
*
|
||||||
|
* @throws NoResponseException
|
||||||
|
* @throws XMPPErrorException
|
||||||
|
* @throws NotConnectedException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public void unpublishAvatar()
|
||||||
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, PubSubException.NotALeafNodeException {
|
||||||
|
getOrCreateMetadataNode().publish(new PayloadItem<>(new MetadataExtension(null)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private final PepListener metadataExtensionListener = new PepListener() {
|
||||||
|
@Override
|
||||||
|
public void eventReceived(EntityBareJid from, EventElement event, Message message) {
|
||||||
|
if (!MetadataExtension.NAMESPACE.equals(event.getNamespace())) {
|
||||||
|
// Totally not of interest for us.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!MetadataExtension.ELEMENT.equals(event.getElementName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ExtensionElement items : event.getExtensions()) {
|
||||||
|
if (!(items instanceof ItemsExtension)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ExtensionElement item : ((ItemsExtension) items).getExtensions()) {
|
||||||
|
if (!(item instanceof PayloadItem<?>)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PayloadItem<?> payloadItem = (PayloadItem<?>) item;
|
||||||
|
|
||||||
|
if (!(payloadItem.getPayload() instanceof MetadataExtension)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MetadataExtension metadataExtension = (MetadataExtension) payloadItem.getPayload();
|
||||||
|
if (metadataStore != null && metadataStore.hasAvatarAvailable(from, ((PayloadItem<?>) item).getId())) {
|
||||||
|
// The metadata store implies that we have a local copy of the published image already. Skip.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (AvatarListener listener : avatarListeners) {
|
||||||
|
listener.onAvatarUpdateReceived(from, metadataExtension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2017 Fernando Ramirez, 2019 Paul Schaub
|
||||||
|
*
|
||||||
|
* 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.packet.XmlEnvironment;
|
||||||
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||||
|
import org.jivesoftware.smackx.avatar.UserAvatarManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data extension element class used to publish avatar image data via PubSub.
|
||||||
|
* Unlike the {@link MetadataExtension}, this class is dedicated to containing the avatar image data itself.
|
||||||
|
*
|
||||||
|
* @author Fernando Ramirez
|
||||||
|
* @see <a href="http://xmpp.org/extensions/xep-0084.html">XEP-0084: User
|
||||||
|
* Avatar</a>
|
||||||
|
*/
|
||||||
|
public class DataExtension implements ExtensionElement {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "data";
|
||||||
|
public static final String NAMESPACE = UserAvatarManager.DATA_NAMESPACE;
|
||||||
|
|
||||||
|
private final byte[] data;
|
||||||
|
private String data_b64; // lazy initialized base64 encoded copy of data
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link DataExtension} from a byte array.
|
||||||
|
*
|
||||||
|
* @param data bytes of the image.
|
||||||
|
*/
|
||||||
|
public DataExtension(byte[] data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link DataExtension} from a base64 encoded String.
|
||||||
|
*
|
||||||
|
* @param base64data bytes of the image as base64 string.
|
||||||
|
*/
|
||||||
|
public DataExtension(String base64data) {
|
||||||
|
this.data_b64 = StringUtils.requireNotNullNorEmpty(base64data,
|
||||||
|
"Base64 String MUST NOT be null, nor empty.");
|
||||||
|
this.data = Base64.decode(base64data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the bytes of the image.
|
||||||
|
*
|
||||||
|
* @return an immutable copy of the image data
|
||||||
|
*/
|
||||||
|
public byte[] getData() {
|
||||||
|
return data.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the image data encoded as a base64 String.
|
||||||
|
*
|
||||||
|
* @return the data as String
|
||||||
|
*/
|
||||||
|
public String getDataAsString() {
|
||||||
|
if (data_b64 == null) {
|
||||||
|
data_b64 = Base64.encodeToString(data);
|
||||||
|
}
|
||||||
|
return data_b64;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence toXML(XmlEnvironment xmlEnvironment) {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
xml.rightAngleBracket();
|
||||||
|
xml.escape(this.getDataAsString());
|
||||||
|
xml.closeElement(this);
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
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.
|
||||||
|
* This class contains metadata about published avatars.
|
||||||
|
*
|
||||||
|
* @author Fernando Ramirez
|
||||||
|
* @see <a href="https://xmpp.org/extensions/xep-0084.html">XEP-0084: User
|
||||||
|
* Avatar</a>
|
||||||
|
*/
|
||||||
|
public class MetadataExtension implements ExtensionElement {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "metadata";
|
||||||
|
public static final String NAMESPACE = UserAvatarManager.METADATA_NAMESPACE;
|
||||||
|
|
||||||
|
private final List<MetadataInfo> infos;
|
||||||
|
private final List<MetadataPointer> pointers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata Extension constructor.
|
||||||
|
*
|
||||||
|
* @param infos
|
||||||
|
*/
|
||||||
|
public MetadataExtension(List<MetadataInfo> infos) {
|
||||||
|
this(infos, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata Extension constructor.
|
||||||
|
*
|
||||||
|
* @param infos
|
||||||
|
* @param pointers
|
||||||
|
*/
|
||||||
|
public MetadataExtension(List<MetadataInfo> infos, List<MetadataPointer> pointers) {
|
||||||
|
this.infos = infos;
|
||||||
|
this.pointers = pointers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the info elements list.
|
||||||
|
*
|
||||||
|
* @return the info elements list
|
||||||
|
*/
|
||||||
|
public List<MetadataInfo> getInfoElements() {
|
||||||
|
return Collections.unmodifiableList(infos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the pointer elements list.
|
||||||
|
*
|
||||||
|
* @return the pointer elements list
|
||||||
|
*/
|
||||||
|
public List<MetadataPointer> getPointerElements() {
|
||||||
|
return (pointers == null) ? null : Collections.unmodifiableList(pointers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence toXML(XmlEnvironment xmlEnvironment) {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
appendInfoElements(xml);
|
||||||
|
appendPointerElements(xml);
|
||||||
|
closeElement(xml);
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendInfoElements(XmlStringBuilder xml) {
|
||||||
|
if (infos != null) {
|
||||||
|
xml.rightAngleBracket();
|
||||||
|
|
||||||
|
for (MetadataInfo info : infos) {
|
||||||
|
xml.halfOpenElement("info");
|
||||||
|
xml.attribute("id", info.getId());
|
||||||
|
xml.attribute("bytes", info.getBytes().longValue());
|
||||||
|
xml.attribute("type", info.getType());
|
||||||
|
xml.optAttribute("url", info.getUrl());
|
||||||
|
|
||||||
|
if (info.getHeight().nativeRepresentation() > 0) {
|
||||||
|
xml.attribute("height", info.getHeight().nativeRepresentation());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.getWidth().nativeRepresentation() > 0) {
|
||||||
|
xml.attribute("width", info.getWidth().nativeRepresentation());
|
||||||
|
}
|
||||||
|
|
||||||
|
xml.closeEmptyElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendPointerElements(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();
|
||||||
|
|
||||||
|
Map<String, Object> fields = pointer.getFields();
|
||||||
|
if (fields != null) {
|
||||||
|
for (Map.Entry<String, Object> pair : fields.entrySet()) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true, if this {@link MetadataExtension} is to be interpreted as Avatar unpublishing.
|
||||||
|
*
|
||||||
|
* @return true if unpublishing, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isDisablingPublishing() {
|
||||||
|
return getInfoElements().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 <a href="http://xmpp.org/extensions/xep-0084.html">XEP-0084: User
|
||||||
|
* Avatar</a>
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smackx.avatar.element;
|
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2019 Paul Schaub
|
||||||
|
*
|
||||||
|
* 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.listener;
|
||||||
|
|
||||||
|
import org.jivesoftware.smackx.avatar.element.MetadataExtension;
|
||||||
|
|
||||||
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener that can notify the user about User Avatar updates.
|
||||||
|
*
|
||||||
|
* @author Paul Schaub
|
||||||
|
*/
|
||||||
|
public interface AvatarListener {
|
||||||
|
|
||||||
|
void onAvatarUpdateReceived(EntityBareJid user, MetadataExtension metadata);
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2019 Paul Schaub
|
||||||
|
*
|
||||||
|
* 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 Paul Schaub
|
||||||
|
* @see <a href="http://xmpp.org/extensions/xep-0084.html">XEP-0084: User
|
||||||
|
* Avatar</a>
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smackx.avatar.listener;
|
|
@ -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 <a href="http://xmpp.org/extensions/xep-0084.html">XEP-0084: User
|
||||||
|
* Avatar</a>
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smackx.avatar;
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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 org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
import org.jivesoftware.smack.provider.ExtensionElementProvider;
|
||||||
|
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
|
import org.jivesoftware.smackx.avatar.element.DataExtension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User Avatar data provider class.
|
||||||
|
*
|
||||||
|
* @author Fernando Ramirez
|
||||||
|
* @see <a href="http://xmpp.org/extensions/xep-0084.html">XEP-0084: User
|
||||||
|
* Avatar</a>
|
||||||
|
*/
|
||||||
|
public class DataProvider extends ExtensionElementProvider<DataExtension> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataExtension parse(XmlPullParser parser, int initialDepth, XmlEnvironment environment)
|
||||||
|
throws IOException, XmlPullParserException {
|
||||||
|
byte[] data = Base64.decode(parser.nextText());
|
||||||
|
return new DataExtension(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2017 Fernando Ramirez, 2019 Paul Schaub
|
||||||
|
*
|
||||||
|
* 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.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
import org.jivesoftware.smack.provider.ExtensionElementProvider;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
|
import org.jivesoftware.smackx.avatar.MetadataInfo;
|
||||||
|
import org.jivesoftware.smackx.avatar.MetadataPointer;
|
||||||
|
import org.jivesoftware.smackx.avatar.element.MetadataExtension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User Avatar metadata provider class.
|
||||||
|
*
|
||||||
|
* @author Fernando Ramirez
|
||||||
|
* @author Paul Schaub
|
||||||
|
* @see <a href="http://xmpp.org/extensions/xep-0084.html">XEP-0084: User
|
||||||
|
* Avatar</a>
|
||||||
|
*/
|
||||||
|
public class MetadataProvider extends ExtensionElementProvider<MetadataExtension> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MetadataExtension parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws IOException, XmlPullParserException {
|
||||||
|
List<MetadataInfo> metadataInfos = null;
|
||||||
|
List<MetadataPointer> pointers = null;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
XmlPullParser.Event eventType = parser.next();
|
||||||
|
|
||||||
|
if (eventType == XmlPullParser.Event.START_ELEMENT) {
|
||||||
|
|
||||||
|
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.Event.END_ELEMENT) {
|
||||||
|
if (parser.getDepth() == initialDepth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MetadataExtension(metadataInfos, pointers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MetadataInfo parseInfo(XmlPullParser parser) throws XmlPullParserException {
|
||||||
|
String id;
|
||||||
|
URL url = null;
|
||||||
|
long bytes = 0;
|
||||||
|
String type;
|
||||||
|
int pixelsHeight = 0;
|
||||||
|
int pixelsWidth = 0;
|
||||||
|
|
||||||
|
id = parser.getAttributeValue("", "id");
|
||||||
|
type = parser.getAttributeValue("", "type");
|
||||||
|
String urlString = parser.getAttributeValue("", "url");
|
||||||
|
if (urlString != null && !urlString.isEmpty()) {
|
||||||
|
try {
|
||||||
|
url = new URL(urlString);
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new XmlPullParserException("Cannot parse URL '" + urlString + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new MetadataInfo(id, url, bytes, type, pixelsHeight, pixelsWidth);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new XmlPullParserException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MetadataPointer parsePointer(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||||
|
int pointerDepth = parser.getDepth();
|
||||||
|
String namespace = null;
|
||||||
|
HashMap<String, Object> fields = null;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
XmlPullParser.Event eventType2 = parser.next();
|
||||||
|
|
||||||
|
if (eventType2 == XmlPullParser.Event.START_ELEMENT) {
|
||||||
|
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.Event.END_ELEMENT) {
|
||||||
|
if (parser.getDepth() == pointerDepth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MetadataPointer(namespace, fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 <a href="http://xmpp.org/extensions/xep-0084.html">XEP-0084: User
|
||||||
|
* Avatar</a>
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smackx.avatar.provider;
|
|
@ -317,6 +317,18 @@
|
||||||
<className>org.jivesoftware.smackx.geoloc.provider.GeoLocationProvider</className>
|
<className>org.jivesoftware.smackx.geoloc.provider.GeoLocationProvider</className>
|
||||||
</extensionProvider>
|
</extensionProvider>
|
||||||
|
|
||||||
|
<!-- XEP-0084: User Avatar -->
|
||||||
|
<extensionProvider>
|
||||||
|
<elementName>data</elementName>
|
||||||
|
<namespace>urn:xmpp:avatar:data</namespace>
|
||||||
|
<className>org.jivesoftware.smackx.avatar.provider.DataProvider</className>
|
||||||
|
</extensionProvider>
|
||||||
|
<extensionProvider>
|
||||||
|
<elementName>metadata</elementName>
|
||||||
|
<namespace>urn:xmpp:avatar:metadata</namespace>
|
||||||
|
<className>org.jivesoftware.smackx.avatar.provider.MetadataProvider</className>
|
||||||
|
</extensionProvider>
|
||||||
|
|
||||||
<!-- XEP-0085: Chat State Notifications -->
|
<!-- XEP-0085: Chat State Notifications -->
|
||||||
<extensionProvider>
|
<extensionProvider>
|
||||||
<elementName>active</elementName>
|
<elementName>active</elementName>
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.smack.xml.XmlPullParser;
|
||||||
|
import org.jivesoftware.smackx.avatar.element.DataExtension;
|
||||||
|
import org.jivesoftware.smackx.avatar.provider.DataProvider;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class DataExtensionTest extends SmackTestSuite {
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
String dataExtensionExample = "<data xmlns='urn:xmpp:avatar:data'>"
|
||||||
|
+ "qANQR1DBwU4DX7jmYZnnfe32"
|
||||||
|
+ "</data>";
|
||||||
|
// @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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,208 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||||
|
import org.jivesoftware.smackx.avatar.element.MetadataExtension;
|
||||||
|
import org.jivesoftware.smackx.avatar.provider.MetadataProvider;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class MetadataExtensionTest {
|
||||||
|
|
||||||
|
private static final String metadataExtensionExample = "<metadata xmlns='urn:xmpp:avatar:metadata'>"
|
||||||
|
+ "<info "
|
||||||
|
+ "id='357a8123a30844a3aa99861b6349264ba67a5694' "
|
||||||
|
+ "bytes='23456' "
|
||||||
|
+ "type='image/gif' "
|
||||||
|
+ "url='http://avatars.example.org/happy.gif' "
|
||||||
|
+ "height='64' "
|
||||||
|
+ "width='128'/>"
|
||||||
|
+ "</metadata>";
|
||||||
|
|
||||||
|
private static final String emptyMetadataExtensionExample = "<metadata xmlns='urn:xmpp:avatar:metadata'/>";
|
||||||
|
|
||||||
|
private static final String metadataWithSeveralInfos = "<metadata xmlns='urn:xmpp:avatar:metadata'>"
|
||||||
|
+ "<info bytes='12345'"
|
||||||
|
+ " height='64'"
|
||||||
|
+ " id='111f4b3c50d7b0df729d299bc6f8e9ef9066971f'"
|
||||||
|
+ " type='image/png'"
|
||||||
|
+ " width='64'/>"
|
||||||
|
+ "<info bytes='12345'"
|
||||||
|
+ " height='64'"
|
||||||
|
+ " id='e279f80c38f99c1e7e53e262b440993b2f7eea57'"
|
||||||
|
+ " type='image/png'"
|
||||||
|
+ " url='http://avatars.example.org/happy.png'"
|
||||||
|
+ " width='128'/>"
|
||||||
|
+ "<info bytes='23456'"
|
||||||
|
+ " height='64'"
|
||||||
|
+ " id='357a8123a30844a3aa99861b6349264ba67a5694'"
|
||||||
|
+ " type='image/gif'"
|
||||||
|
+ " url='http://avatars.example.org/happy.gif'"
|
||||||
|
+ " width='64'/>"
|
||||||
|
+ "</metadata>";
|
||||||
|
|
||||||
|
private static final String metadataWithInfoAndPointers = "<metadata xmlns='urn:xmpp:avatar:metadata'>"
|
||||||
|
+ "<info"
|
||||||
|
+ " id='111f4b3c50d7b0df729d299bc6f8e9ef9066971f'"
|
||||||
|
+ " bytes='12345'"
|
||||||
|
+ " type='image/png'"
|
||||||
|
+ " height='64'"
|
||||||
|
+ " width='64'/>"
|
||||||
|
+ "<pointer>"
|
||||||
|
+ "<x xmlns='http://example.com/virtualworlds'>"
|
||||||
|
+ "<game>Ancapistan</game>"
|
||||||
|
+ "<character>Kropotkin</character>"
|
||||||
|
+ "</x>"
|
||||||
|
+ "</pointer>"
|
||||||
|
+ "<pointer>"
|
||||||
|
+ "<x xmlns='http://sample.com/game'>"
|
||||||
|
+ "<level>hard</level>"
|
||||||
|
+ "<players>2</players>"
|
||||||
|
+ "</x>"
|
||||||
|
+ "</pointer>"
|
||||||
|
+ "</metadata>";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkMetadataExtensionParse() throws Exception {
|
||||||
|
String id = "357a8123a30844a3aa99861b6349264ba67a5694";
|
||||||
|
URL url = new 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<MetadataInfo> 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.getInfoElements().get(0).getId());
|
||||||
|
Assert.assertEquals(url, metadataExtensionFromProvider.getInfoElements().get(0).getUrl());
|
||||||
|
Assert.assertEquals(bytes, metadataExtensionFromProvider.getInfoElements().get(0).getBytes().intValue());
|
||||||
|
Assert.assertEquals(type, metadataExtensionFromProvider.getInfoElements().get(0).getType());
|
||||||
|
Assert.assertEquals(pixelsHeight, metadataExtensionFromProvider.getInfoElements().get(0).getHeight().intValue());
|
||||||
|
Assert.assertEquals(pixelsWidth, metadataExtensionFromProvider.getInfoElements().get(0).getWidth().intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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.getInfoElements().get(0);
|
||||||
|
MetadataInfo info2 = metadataExtensionFromProvider.getInfoElements().get(1);
|
||||||
|
MetadataInfo info3 = metadataExtensionFromProvider.getInfoElements().get(2);
|
||||||
|
|
||||||
|
Assert.assertEquals("111f4b3c50d7b0df729d299bc6f8e9ef9066971f", info1.getId());
|
||||||
|
Assert.assertNull(info1.getUrl());
|
||||||
|
Assert.assertEquals(12345, info1.getBytes().intValue());
|
||||||
|
Assert.assertEquals("image/png", info1.getType());
|
||||||
|
Assert.assertEquals(64, info1.getHeight().intValue());
|
||||||
|
Assert.assertEquals(64, info1.getWidth().intValue());
|
||||||
|
|
||||||
|
Assert.assertEquals("e279f80c38f99c1e7e53e262b440993b2f7eea57", info2.getId());
|
||||||
|
Assert.assertEquals(new URL("http://avatars.example.org/happy.png"), info2.getUrl());
|
||||||
|
Assert.assertEquals(12345, info2.getBytes().intValue());
|
||||||
|
Assert.assertEquals("image/png", info2.getType());
|
||||||
|
Assert.assertEquals(64, info2.getHeight().intValue());
|
||||||
|
Assert.assertEquals(128, info2.getWidth().intValue());
|
||||||
|
|
||||||
|
Assert.assertEquals("357a8123a30844a3aa99861b6349264ba67a5694", info3.getId());
|
||||||
|
Assert.assertEquals(new URL("http://avatars.example.org/happy.gif"), info3.getUrl());
|
||||||
|
Assert.assertEquals(23456, info3.getBytes().intValue());
|
||||||
|
Assert.assertEquals("image/gif", info3.getType());
|
||||||
|
Assert.assertEquals(64, info3.getHeight().intValue());
|
||||||
|
Assert.assertEquals(64, info3.getWidth().intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkInfosAndPointersParse() throws Exception {
|
||||||
|
XmlPullParser parser = PacketParserUtils.getParserFor(metadataWithInfoAndPointers);
|
||||||
|
MetadataExtension metadataExtensionFromProvider = new MetadataProvider().parse(parser);
|
||||||
|
|
||||||
|
MetadataInfo info = metadataExtensionFromProvider.getInfoElements().get(0);
|
||||||
|
Assert.assertEquals("111f4b3c50d7b0df729d299bc6f8e9ef9066971f", info.getId());
|
||||||
|
Assert.assertNull(info.getUrl());
|
||||||
|
Assert.assertEquals(12345, info.getBytes().intValue());
|
||||||
|
Assert.assertEquals("image/png", info.getType());
|
||||||
|
Assert.assertEquals(64, info.getHeight().intValue());
|
||||||
|
Assert.assertEquals(64, info.getWidth().intValue());
|
||||||
|
|
||||||
|
MetadataPointer pointer1 = metadataExtensionFromProvider.getPointerElements().get(0);
|
||||||
|
Map<String, Object> 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.getPointerElements().get(1);
|
||||||
|
Map<String, Object> 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<String, Object> fields1 = new HashMap<>();
|
||||||
|
fields1.put("game", "Ancapistan");
|
||||||
|
fields1.put("character", "Kropotkin");
|
||||||
|
MetadataPointer pointer1 = new MetadataPointer("http://example.com/virtualworlds", fields1);
|
||||||
|
|
||||||
|
HashMap<String, Object> fields2 = new HashMap<>();
|
||||||
|
fields2.put("level", "hard");
|
||||||
|
fields2.put("players", 2);
|
||||||
|
MetadataPointer pointer2 = new MetadataPointer("http://sample.com/game", fields2);
|
||||||
|
|
||||||
|
List<MetadataInfo> infos = new ArrayList<>();
|
||||||
|
infos.add(info);
|
||||||
|
|
||||||
|
List<MetadataPointer> pointers = new ArrayList<>();
|
||||||
|
pointers.add(pointer1);
|
||||||
|
pointers.add(pointer2);
|
||||||
|
|
||||||
|
MetadataExtension metadataExtension = new MetadataExtension(infos, pointers);
|
||||||
|
Assert.assertEquals(metadataWithInfoAndPointers, metadataExtension.toXML().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue