0

I am developing an application that reads the XML file and creates the Hash ID based on the details present in XML. As of now, everything is working perfectly, and able to get the List<String>.

I would like to convert this application into Reactive Streams using the Smallrye Mutiny so I went through some of the documentation but did not understand clearly how to convert this application into Reactive Streams where I do not have to wait for the completion of all XML file to return the List<String>. Rather I can start returning the Multi<String> as and when the its generated.

Following is the simple XML that I am reading using SAX Parser to create the Hash ID:

<customerList>
    <customer>
        <name>Batman</name>
        <age>25</age>
    </customer>
    <customer>
        <name>Superman</name>
        <age>28</age>
    </customer>
</customerList>

Following is the Main application which will make a call to SaxHandler:

public Multi<String> xmlEventHashGenerator(final InputStream xmlStream) throws SAXException, ParserConfigurationException, IOException {
    final SAXParserFactory factory = SAXParserFactory.newInstance();
    final SaxHandler saxHandler = new SaxHandler();
    factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
    factory.newSAXParser().parse(xmlStream, saxHandler);

    return Multi.createFrom().emitter(em ->{
        saxHandler.getRootNodes().forEach(contextNode -> {
            final String preHashString = contextNode.toString();
            try {
                final StringBuilder hashId = new StringBuilder();
                MessageDigest.getInstance("SHA-256").digest(preHashString.getBytes(StandardCharsets.UTF_8));
                hashId.append(DatatypeConverter.printHexBinary(digest).toLowerCase());
                em.emit(hashId.toString());
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
        });
        em.complete();
    });
}

Following is the SaxHandler which will read the XML and create HashIDs:

public class SaxHandler extends DefaultHandler {
    @Getter
    private final List<String> eventHashIds = new ArrayList<>();
    @Getter
    private final List<ContextNode> rootNodes = new ArrayList<>();

    private final HashMap<String, String> contextHeader = new HashMap<>();
    private final String hashAlgorithm;
    private ContextNode currentNode = null;
    private ContextNode rootNode = null;
    private final StringBuilder currentValue = new StringBuilder();

    public SaxHandler(final String hashAlgorithm) {
        this.hashAlgorithm = hashAlgorithm;
    }

    @Override
    public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) {
        if (rootNode == null && qName.equals("customer")) {
            rootNode = new ContextNode(contextHeader);
            currentNode = rootNode;
            rootNode.children.add(new ContextNode(rootNode, "type", qName));
        }else if (currentNode != null) {
            ContextNode n = new ContextNode(currentNode, qName, (String) null);
            currentNode.children.add(n);
            currentNode = n;
        }
    }

    @Override
    public void characters(char[] ch, int start, int length) {
        currentValue.append(ch, start, length);
    }

    @Override
    public void endElement(final String uri, final String localName, final String qName) {

        if (rootNode != null && !qName.equals("customer")) {
            final String value = !currentValue.toString().trim().equals("") ? currentValue.toString().trim() : null;
            currentNode.children.add(new ContextNode(currentNode, qName, value));
        }

        if (qName.equals("customer")) {
            rootNodes.add(rootNode);
            rootNode = null;
        }
        currentValue.setLength(0);
    }
}

Following is the Test:

@Test
    public void xmlTest() throws Exception {
        final HashGenerator eventHashGenerator = new HashGenerator();
        final InputStream xmlStream = getClass().getResourceAsStream("/customer.xml");
        final List<String> eventHashIds = eventHashGenerator.xmlHashGenerator(xmlStream, "sha3-256");
        System.out.println("\nGenerated Event Hash Ids : \n" + eventHashIds);
    }

Can someone please guide me to some example or provide some idea on how to convert this application into SmallRye Mutinty Multi<String> based application?

BATMAN_2008
  • 2,788
  • 3
  • 31
  • 98
  • I feel like you are asking us to rewrite this code for you. What have you tried so far? Have you tried to convert at least some of the methods? – Davide D'Alto Jun 24 '22 at 13:11
  • [This guide on Vert.x and Mutiny](https://quarkus.io/blog/mutiny-vertx/#the-vert-x-bare-api-and-friends) and [this question on stackoverflow](https://stackoverflow.com/questions/45677070/read-large-file-using-vertx) should give you a good starting point – Davide D'Alto Jun 24 '22 at 13:23
  • @DavideD'Alto Thanks a lot for your response. I have modified my code and tried to use `Multi.createFrom().emitter(em ->{` but I am not sure if this is the best approach as I need to wait for whole list to complete. Can you please suggest if there is any better approach to return as soon as the HashId has been created? – BATMAN_2008 Jun 27 '22 at 10:58
  • I've answered with how I would refactor the code. – Davide D'Alto Jun 27 '22 at 14:51

1 Answers1

1

I think you can refactor xmlEventHashGenerator to

    public Multi<String> xmlEventHashGenerator(final InputStream xmlStream) throws SAXException, ParserConfigurationException, IOException {
        final SAXParserFactory factory = SAXParserFactory.newInstance();
        final SaxHandler saxHandler = new SaxHandler();
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        factory.newSAXParser().parse(xmlStream, saxHandler);

        return Multi.createFrom()
                .iterable( saxHandler.getRootNodes() )
                .map( RootNode::toString )
                .map( this::convertDatatype );
    }

    private String convertDatatype(String preHashString) {
        try {
            // I think we could create the MessageDigest instance only once
            byte[] digest = MessageDigest.getInstance( "SHA-256" )
                    .digest( preHashString.getBytes( StandardCharsets.UTF_8 ) );
            return DatatypeConverter.printHexBinary( digest ).toLowerCase();
        }
        catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException( e );
        }
    }

The test method will look something like:

    @Test
    public void xmlTest() throws Exception {
        final HashGenerator eventHashGenerator = new HashGenerator();
        final InputStream xmlStream = getClass().getResourceAsStream("/customer.xml");
        System.out.println("Generated Event Hash Ids: ");
        eventHashGenerator
            .xmlHashGenerator(xmlStream)
            // Print all the hash codes
            .invoke( hash -> System.out.println( hash )
            .await().indefinitely();
     }

But if you want to concatenate all the hash codes, you can do:

    @Test
    public void xmlTest() throws Exception {
        final HashGenerator eventHashGenerator = new HashGenerator();
        final InputStream xmlStream = getClass()
            .getResourceAsStream("/customer.xml");
        String hash = eventHashGenerator
            .xmlHashGenerator(xmlStream)
            // Concatenate all the results
            .collect().with( Collectors.joining() );
            // Print the hashcode
            .invoke( hashcode -> System.out.println("\nGenerated Event Hash Ids : \n" + hashcode) )
            .await().indefinitely();
     }
Davide D'Alto
  • 7,421
  • 2
  • 16
  • 30