powered by Jive Software

Xmlns lost when StandardExtensionElement Message is sent

  • Smack version : 4.3.4

Xml payload I send with “sendStanza” method :

<message to='foo@bar.foobar.com' id='2793eaf8-8b55-4e14-8b9c-e8fc4be2250c' type='groupchat'>
    <body></body>
    <event xmlns='http://foo.com/protocol/events#card_sent'>
        <card xmlns='http://foo.com/protocol/card'>
            <title>bar</title>
            <text>bar</text>
            <image xmlns='http://foo.com/protocol/card_image'>
                <url>bar</url>
                <description>bar</description>
            </image>
            <actions xmlns='http://foo.com/protocol/actions'>
                <action xmlns='http://foo.com/protocol/action#link'>
                    <title>bar</title>
                    <url>bar</url>
                </action>
                <action xmlns='http://foo.com/protocol/action#link'>
                    <title>bar</title>
                    <url>bar</url>
                </action>
                <action xmlns='http://foo.com/protocol/action#link'>
                    <title>bar</title>
                    <url>bar</url>
                </action>
                <action xmlns='http://foo.com/protocol/action#link'>
                    <title>bar</title>
                    <url>bar</url>
                </action>
                <action xmlns='http://foo.com/protocol/action#link'>
                    <title>bar</title>
                    <url>bar</url>
                </action>
                <action xmlns='http://foo.com/protocol/action#link'>
                    <title>bar</title>
                    <url>bar</url>
                </action>
            </actions>
        </card>
    </event>
</message>

Xml payload really sent to Xmpp (from log.debug) :

<message to='foo@bar.foobar.com' id='2793eaf8-8b55-4e14-8b9c-e8fc4be2250c' type='groupchat'>
    <body></body>
    <event xmlns='http://foo.com/protocol/events#card_sent'>
        <card xmlns='http://foo.com/protocol/card'>
            <title>bar</title>
            <text>bar</text>
            <image xmlns='http://foo.com/protocol/card_image'>
                <url>bar</url>
                <description>bar</description>
            </image>
            <actions xmlns='http://foo.com/protocol/actions'>
                <action xmlns='http://foo.com/protocol/action#link'>
                    <title>bar</title>
                    <url>bar</url>
                </action>
                <action>
                    <title>bar</title>
                    <url>bar</url>
                </action>
                <action>
                    <title>bar</title>
                    <url>bar</url>
                </action>
                <action>
                    <title>bar</title>
                    <url>bar</url>
                </action>
                <action>
                    <title>bar</title>
                    <url>bar</url>
                </action>
                <action>
                    <title>bar</title>
                    <url>bar</url>
                </action>
            </actions>
        </card>
    </event>
</message>

As you can see, the “xmlns” from “actions -> action” are missing (except for the first one that is still present).

  • The relevant code parts :

XmlStringBuilder.java :

public void write(Writer writer, String enclosingNamespace) throws IOException {
        for (CharSequence csq : sb.getAsList()) {
            if (csq instanceof XmlStringBuilder) {
                ((XmlStringBuilder) csq).write(writer, enclosingNamespace);
            }
            else if (csq instanceof XmlNsAttribute) {
                XmlNsAttribute xmlNsAttribute = (XmlNsAttribute) csq;
                if (!xmlNsAttribute.value.equals(enclosingNamespace)) {
                    writer.write(xmlNsAttribute.toString());
                    enclosingNamespace = xmlNsAttribute.value;
                }
            }
            else {
                writer.write(csq.toString());
            }
        }
    }

I ran the code on debug mode :
When I loop on my “actions -> action” => xmlNsAttribute.equals(enclosingNamespace) and so, the code writer.write(xmlNsAttribute.toString()); is never executed after the first “action”.

To validate my guess, I went back to the smackVersion=4.2.4. And the payload really sent was equal to the payload I wanted to send (ie : with xmlns still present / Test OK).

Here is the code of the write method in 4.2.4 :

public void write(Writer writer) throws IOException {
        for (CharSequence csq : sb.getAsList()) {
            if (csq instanceof XmlStringBuilder) {
                ((XmlStringBuilder) csq).write(writer);
            }
            else {
                writer.write(csq.toString());
            }
        }
    }

That appears to be a bug indeed.

This should be fixed in Smack’s current master branch. The following test passes:

    @Test
    public void equalInnerNamespaceTest() {
        StandardExtensionElement innerOne = StandardExtensionElement.builder("inner", "inner-namespace").build();
        StandardExtensionElement innerTwo = StandardExtensionElement.builder("inner", "inner-namespace").build();

        StandardExtensionElement outer = StandardExtensionElement.builder("outer", "outer-namespace").addElement(
                        innerOne).addElement(innerTwo).build();

        String expectedXml = "<outer xmlns='outer-namespace'><inner xmlns='inner-namespace'></inner><inner xmlns='inner-namespace'></inner></outer>";
        XmlStringBuilder actualXml = outer.toXML(XmlEnvironment.EMPTY);

        XmlUnitUtils.assertXmlSimilar(expectedXml, actualXml);

        StringBuilder actualXmlTwo = actualXml.toXML(XmlEnvironment.EMPTY);

        XmlUnitUtils.assertXmlSimilar(expectedXml, actualXmlTwo);
    }

I tried the last version (4.4.0-alpha2), and it didn’t work either. I also have these kind of Unit Tests in my project, and they are all in Success (version >= 4.3.4).

But your test is only testing the method toXML, and not the actual sendMessage.

The issue here, is really in the “XmlStringBuilder => write()” method which is called in “XMPPTCPConnection.java => writePackets()”. The xml payload is modified by the “write” method just before to be send to the XMPP server, and that is causing the issue.

4.4.0-alpha2 is already a few commits behind the current master. Please try the latest master, e.g. by using the snapshots or a composite build.

@Flow Where can I find the latest snapshot ? The latest I can find in maven repositories is the “4.4.0-alpha2”

The latest snapshots can be found here.
You probably want to include the snapshot repo in your build script.

In gradle that would look like this:

repositories {
    maven {
        url 'https://igniterealtime.org/repo/'
    }
}
dependencies {
    implementation "org.igniterealtime.smack:smack-core:4.4.0-alpha3-20190909.010503-7"
    ...
}

Ok thanks @Paul_Schaub, I’ll try it!

Note that due to the way the subprojects are built, the exact version string differs from module to module (i.e. smack-core has a different version string than smack-experimental etc.).

If you want to fetch a list of the most recent version strings for a given snapshot release, you can use the script below:

smack-unique-snapshots.sh

#!/bin/sh
if [ $# -eq 0 ]; then
    echo "Usage: smack-unique-snapshots.sh 4.4.0-alpha1-SNAPSHOT";
    exit 1;
fi

VERSION=$1
SMACK="https://igniterealtime.org/repo/org/igniterealtime/smack"
META="maven-metadata.xml"

TEMPD=$(mktemp -d)
cd $TEMPD
# Determine avail smack projects by recursively fetching subdirs
wget -q -r -l1 -nH --cut-dirs=4 --no-parent -e robots=off $SMACK

PROJECTS=()
VERSIONS=()
# smack projects will result in subdir
# get index.html of project with descending change date
# from the html, extract first .pom file name, which is latest version
for d in `ls` ; do
    if  [[ -d $d ]]; then
        PRO=$(wget -q "$SMACK/$d/$VERSION?C=M;O=D" -O- | grep -Po '(?<=a href=").*?(?=\.pom")' | head -n1);

        # Repair missing projects
        if [ ${#PRO} -le ${#d} ]; then
            PRO="$d MISSING"
        fi
        PROJECTS+=("$PRO")

        echo $PRO

        # Gradle dependency generation
        # Determine snapshot version string, which begins after project name
        PROJ_LEN=$((${#d} + 1))
        SNAP=${PRO:$PROJ_LEN}
        # Make project name camelcase and append version string
        VER="$(echo $d | sed -r 's/(^|-)(\w)/\U\2/g')Version = \"$SNAP\""
        # Append to array with first char lower case'd
        VERSIONS+=("${VER,}")
    fi
done
# print out block of gradle dependecy versions
printf "\n"
echo "Gradle Versions:"
for v in "${VERSIONS[@]}"; do
    echo $v
done

# clean up
rm -rf $TEMPD