Project

Profile

Help

Handling Map Parameters Passed to Custom Extension Instructions: Questions re Types

Added by Anthony Bufort 10 days ago

Hello All,

Whilst writing my custom extension function (Java-based), I expected to follow the following model for my call method

public class MyExtensionFunction extends ExtensionFunctionDefinition {

    @Override
    public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException {
        // Retrieve the map parameter passed from XSLT
        XdmMap map = (XdmMap) arguments[0].head().getItem();

        // Process the map parameter
        for (String key : map.keySet()) {
            Sequence value = map.get(key);
            // Process key-value pairs as needed
        }

        // Your logic here

        return null; // Return the result of your extension function
    }

    @Override
    public SequenceType[] getArgumentTypes() {
        return new SequenceType[]{SequenceType.makeSequenceType(MapType.ANY_MAP_TYPE, StaticProperty.ALLOWS_ZERO_OR_MORE)};
    }

    @Override
    public SequenceType getResultType(SequenceType[] suppliedArgumentTypes) {
        return SequenceType.SINGLE_ATOMIC;
    }
}

...which is largely correct, except for the type of the map parameter coming in. It is not an XdmMap, but rather a HashTrieMap.

I have adapted, and both the function and the extension instruction variants now work using HashTrieMap. However, I'd like to gain a deeper understanding of what's going on.

Is there something in particular that I am doing (other than the simple fact that I am passing a map) to provoke that type? Is this just the way it always is - maps come in as HashTrieMaps - or can I change the type to come in as XdmMap somehow? I have so far found no way to convert, either by casting or by any provided utility method, a HashTrieMap to an XdmMap. Not that I NEED to do that at this point, but I want to know these type and any best-practices in-depth beyond what I have learned so far by looking at documentation.

I suspect there might be some clever way to easily convert between map types that I'm missing here... or perhaps not.

Thanks in Advance,

-Tony Bufort


Replies (4)

Please register to reply

RE: Handling Map Parameters Passed to Custom Extension Instructions: Questions re Types - Added by Michael Kay 10 days ago

Saxon has two layers of API, which we might call the system programming interface (SPI) and the application programming interface (API - specifically, for Java, s9api). When you write deeply embedded code like integrated extension functions, you are using the SPI.

At the s9api level, XDM values are represented by classes such as XdmValue, XdmNode, XdmItem, XdmAtomicValue, XdmMap. These are in fact wrappers around the SPI representations, where you find classes such as Sequence, Item, NodeInfo, NumericValue, StringValue, BooleanValue.

At the SPI level an XDM map is represented by an object of class MapItem. There are a number of implementations of MapItem, one of which is HashTrieMap. But you shouldn't assume that this will always be the implementation; you should only assume MapItem.

RE: Handling Map Parameters Passed to Custom Extension Instructions: Questions re Types - Added by Anthony Bufort 9 days ago

Okay, thank you, Michael.

I have reworked what I'm doing, and now use MapItem. Using HashTrieMap did not seem right to me, and your explanation was just what I needed.

	public static PutObjectResult uploadToS3Bucket(String bucketName, String keyName, String filePath, 
			MapItem metadataMap) throws XPathException {
		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 != null) {
			AtomicIterator keys = metadataMap.keys();
			
			for (AtomicValue key : keys.next()) {
				metadata.addUserMetadata(key.getStringValue(), metadataMap.get(key).getStringValue());
			}
			
			putRequest.withMetadata(metadata);
		}
		
		PutObjectResult result = s3Client.putObject(putRequest);
		
		return result;
	}

-Tony

RE: Handling Map Parameters Passed to Custom Extension Instructions: Questions re Types - Added by Michael Kay 9 days ago

The for (AtomicValue key : keys.next()) { looks wrong...

And you could perhaps shave off a couple of microseconds by doing

for (KeyValuePair kvp : metadataMap.keyValuePairs()) {
	metadata.addUserMetadata(kvp.key.getStringValue(), kvp.value.getStringValue());
}

RE: Handling Map Parameters Passed to Custom Extension Instructions: Questions re Types - Added by Anthony Bufort 9 days ago

While what I had was tested and working, I have now refactored my code to use KeyValuePair as suggested.

Thank You! I sincerely appreciate the help,

-Tony

    (1-4/4)

    Please register to reply