Project

Profile

Help

Passing A Map Parameter from XSLT to a Custom Extension Element Instruction

Added by Anthony Bufort 5 months ago

Saxon Version : EE 12.4 (Java) XSLT Version : 3.1

Hello All,

I am having issues passing a parameter of type map(xs:string, xs:string) from XSLT to a custom extension-element instruction.

First, my XSLT:

<xsl:variable name="metadata-map" as="map(xs:string, xs:string)">
            <xsl:map>
                <xsl:map-entry key="key-1" select="value-1"/>
                <xsl:map-entry key="key-2" select="value-2"/>
                <xsl:map-entry key="key-3" select="value-3"/>
            </xsl:map>
</xsl:variable>

...OR...

<xsl:variable name="metadata-map" select="map{'key1': 'value1', 'key2': 'value2'}" as="map(xs:string, xs:string)"/>

...and then...

**<ajb:functionality-needing-map metadata="{$metadata-map}"/>**

Now, in the target Java class, which extends ExtensionInstruction , I have the following override for call ()

                @Override
		public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException {
			String bucketName = str(arguments[0]);
			String keyName = str(arguments[1]);
			String filePath = str(arguments[2]);
			XdmMap metadata = (XdmMap) arguments[3].head().itemAt(0);
			String result = null;
			
			try {
				if (bucketName.length() > 0 && keyName.length() > 0 && filePath.length() > 0 && metadata.size() > 0)
					result = AmazonWebServicesOps.uploadToS3Bucket(bucketName, keyName, filePath, metadata);
			} catch (Exception e) {
				throw (new XPathException("Problem executing aws-upload-to-s3-bucket command", e))
						.withXPathContext(context).withErrorCode("AJB002").withLocation(getLocation());
			}

			return StringValue.makeStringValue(result);
		}

When I attempt to run all in oXygen 26.1, I get:

"Cannot atomize a map"

...which I know, but I cannot figure out what I am doing to make the processor believe I am trying to.

Current state of target class attached.

Thank You,

-Tony AJB Consulting

/xslt-commands/src/main/java/us/ajbconsulting/xslt/extensions/exposures/instructions/aws/s3/UploadToS3Bucket.java


Replies (8)

Please register to reply

Passing A Map Parameter from XSLT to a Custom Extension Element Instruction - Added by Norm Tovey-Walsh 5 months ago

Saxonica Developer Community writes:

Now, in the target Java class, which extends ExtensionInstruction , I have the following override
for call ()

My guess is that this is the culprit:

result = AmazonWebServicesOps.uploadToS3Bucket(bucketName, keyName, filePath, metadata);

I don’t know what API that is, exactly, but what the upload function is going to need to do is turn ‘metadata’ into a sequence of bytes somehow and the obvious way is by attempting to serialize it.

If that’s the case, a likely-to-succeed workaround is going to be to serialize the map yourself (using method=json, for example) and then pass the serialized, string value to the uploadToS3Bucket function.

Hope that helps.

Be seeing you,
norm

--
Norm Tovey-Walsh
Saxonica

RE: Passing A Map Parameter from XSLT to a Custom Extension Element Instruction - Added by Anthony Bufort 5 months ago

Hello Norm - thanks for the reply and turning some attention to this.

Here is the missing piece I should have included previously:

       public static PutObjectResult uploadToS3Bucket(String bucketName, String keyName, String filePath, 
			XdmMap metadataMap) {
		if (! getIsInited()) {
			init();
		}
		
		// disable md5 check first
		System.setProperty(SkipMd5CheckStrategy.DISABLE_PUT_OBJECT_MD5_VALIDATION_PROPERTY, "true");

		PutObjectRequest putRequest = new PutObjectRequest(bucketName, keyName, new File(filePath));
		ObjectMetadata metadata = new ObjectMetadata();
		
		if (metadataMap.mapSize() > 0) {
			metadataMap.keySet().forEach((key) -> metadata.addUserMetadata(key.getStringValue(), 
					metadataMap.get(key).getUnderlyingValue().toString()));
		}
		
		putRequest.withMetadata(metadata);

		PutObjectResult result = s3Client.putObject(putRequest);
		
		return result;
	}

So I am passing an XDM map arg to this method, then pouring its contents into AWS's ObjectMetadata structure via the for loop. In short, I'm not seeing anything controversial here.

I think the issue lies somewhere in between ** the call within the XSLT** and previous to the point we've just gone over . And after a good night's sleep and more review, I think I've tracked it down.

It has to do with the way I take in my map argument and process it through the compile(Compilation exec, ComponentDeclaration decl) method, among other things.

The crux, in English rather than more code, is that I do not know where to receive the map once it's left the XSLT. Where do I properly catch and process the value?

For example, I know that traditional string-type attributes are set within prepareAttributes (), and I know how to successfully get the values of those. From the examples I've seen to date, however, I am just not clear on how to accept and carry through a map type attribute through to compile and beyond.

Attached is the current state of UploadToS3Bucket.java .

Thanks Again,

-Tony

RE: Passing A Map Parameter from XSLT to a Custom Extension Element Instruction - Added by Michael Kay 5 months ago

I think the essence of the problem is that you have an attribute value template:

<ajb:functionality-needing-map metadata="{$metadata-map}"/>

and the semantics of an AVT are that it constructs a string by atomizing the result of the enclosed expression; and atomizing a map fails.

It should be possible to design an extension instruction so that the value of the metadata attribute is an expression rather than an AVT, which means you would call it as:

<ajb:functionality-needing-map metadata="$metadata-map"/>

I would expect to see in your ExtensionInstruction class:

        Expression bucketNameExp;
	Expression keyNameExp;
	Expression filePathExp;
	Expression metadataMapExp;

with code in prepareAttributes that uses makeExpression() rather than makeAttributeValueTemplate().

RE: Passing A Map Parameter from XSLT to a Custom Extension Element Instruction - Added by Anthony Bufort 5 months ago

AHA! That was the missing info I needed. Now I understand the atomization error, and the solution.

Will redesign to move to an expression-based intake.

Once again, thank you, Mike! :)

-Tony Bufort

RE: Passing A Map Parameter from XSLT to a Custom Extension Element Instruction - Added by Norm Tovey-Walsh 5 months ago

if (metadataMap.mapSize() > 0) {
metadataMap.keySet().forEach((key) ->
metadata.addUserMetadata(key.getStringValue(),
metadataMap.get(key).getUnderlyingValue().toString()));
}

So I am passing an XDM map arg to this method, then pouring its contents into AWS's ObjectMetadata structure via the for loop. In short, I'm not seeing anything controversial here.

I’m grasping at straws a little bit here, but is it possible that your map contains a value that is a map? If so, then that toString() call above could be the culprit.

Given that you want to serialize the map, a more flexible solution might be to create a Serializer, tell it to use the JSON output method, and let it serialize the map.

For example, I know that traditional string-type attributes are set within prepareAttributes (), and I know how to successfully get the values of those. From the examples I've seen to date, however, I am just not clear on how to accept and carry through a map type attribute through to compile and beyond.

I think what you’re doing looks okay. You can get the keys and the values. But if you might have nested maps (or arrays nested in maps, or maps nested in arrays nested in maps, etc.) you have to work your way over the structure recursively.

Be seeing you,
norm

--
Norm Tovey-Walsh
Saxonica

RE: Passing A Map Parameter from XSLT to a Custom Extension Element Instruction - Added by Norm Tovey-Walsh 5 months ago

I think the essence of the problem is that you have an attribute value template:

<ajb:functionality-needing-map metadata="{$metadata-map}"/>

Well, don’t I feel like an idiot this afternoon :-)

Thank you, Michael!

Be seeing you,
norm

--
Norm Tovey-Walsh
Saxonica

RE: Passing A Map Parameter from XSLT to a Custom Extension Element Instruction - Added by Anthony Bufort 5 months ago

Yeah, I don't believe in that nonsense, Norm. No idiots, most especially when trying to help. LOL I thoroughly appreciate the feedback and the attempt.

I will also keep what you said in mind for the future, for as my extension set grows, that might come into play.

I have a growing set of functionality for XSLT involving four major cloud providers - Amazon, Microsoft Azure, Google, and IBM. Exposing them as both functions and elements.

Happy 4th, BTW! Try not to let the state of American politics get to you. ;)

Take Care,

-Tony

RE: Passing A Map Parameter from XSLT to a Custom Extension Element Instruction - Added by Michael Kay 5 months ago

Norm, FWIW I drafted a very similar reply myself before finding the true answer.

    (1-8/8)

    Please register to reply