Process an ACI Response

The API uses a processor based system to consume the responses from an ACI Server. It is based around two interfaces: Processor<T> and StAXProcessor<T>. StAXProcessor<T> has an abstract implementation that sets up the StAX parser. You can either write your own processor implementations, or use those that are included with the API.

The default processor that is included with the API is the DocumentProcessor. This processor returns a DOM Document object, which you can then parse using either DOM or XPath. However, not all ACI responses are XML. For example, the cluster and spectrograph functionality can also return images. The SDK contains a BinaryResponseProcessor for these cases. Internally it uses the ByteArrayProcessor to process the binary data, or the ErrorProcessor if it detects that the response is actually XML.

The following example shows the use of the DocumentProcessor and XPath to output query results to the console:

// Send the action and process the response into a DOM Document...
final Document response = aciService.executeAction(
      parameters, new DocumentProcessor());

// Get the individual documents in the response...
final XPath xpath = XPathFactory.newInstance().newXPath();
final NodeList nodeList = (NodeList) xpath.evaluate(
      "/autnresponse/responsedata/hit",
      response,
      XPathConstants.NODESET
);
final int documents = nodeList.getLength();

System.out.println(
      "Displaying " + documents +
      " results from a total of " +
      xpath.evaluate("/autnresponse/responsedata/totalhits", results) + '\n'
);

// Output the details of each document to the console...
for(int ii = 0; ii < documents; ii++) {
    // Get an individual document in the response...
    final Node node = nodeList.item(ii);

    // Output a few key fields...
    System.out.println('\n' + xpath.evaluate("reference", node));
    System.out.println("Title     : " + xpath.evaluate("title", node));
    System.out.println("Relevance : " + xpath.evaluate("weight", node) + '%');
    System.out.println("Database  : " + xpath.evaluate("database", node));
    final String links = xpath.evaluate("links", node);
    if(StringUtils.isNotBlank(links)) {
        System.out.println("Matched on: " + links);
    }
    System.out.println("Summary   : " + xpath.evaluate(
          "summary", node).replaceAll("\n", " "));
}

The following example shows the use of the BinaryResponseProcessor in a Spring based service implementation:

@Service
public class ClusteringServiceImpl implements ClusteringService {

    @Autowired
    @Qualifier("clusterService")
    private AciService clusteringService;

    ...

    @Override
    public byte[] clusterServe2DMap(final String job) {
        return clusteringService.executeAction(
              new ActionParameters(
                    new AciParameter(
                          AciConstants.PARAM_ACTION, "ClusterServe2DMap"),
                    new AciParameter("sourceJobName", job)
              ),
              new BinaryResponseProcessor()
        ); 
    }

    ...
}

You might want to write your own response processors for increased performance, or to return the response as a collection of JavaBeans. In this case, you simply implement one of the two processor interfaces. When you need access to the underlying response stream, implement the Processor<T> interface. When you want to parse XML, then extend the AbstractStAXProcessor<T> class, which implements the StAXProcessor<T> interface.

The following example shows how to return the first instance of a field in a response. This process is useful for pulling out information such as SecurityInfo strings, where you do not need the rest of the response.

public class ElementValueProcessor extends AbstractStAXProcessor<String> {

    private final String elementName;

    public ElementValueProcessor(final String elementName) {
        this.elementName = elementName;
        setErrorProcessor(new ErrorProcessor());
    }

    @Override
    public String process(final XMLStreamReader xmlStreamReader) {
        try {
            if(isErrorResponse(xmlStreamReader)) {
                processErrorResponse(xmlStreamReader);
            }

            String result = null;

            while(xmlStreamReader.hasNext()) {
                final int elementType = xmlStreamReader.next();

                if((elementType == XMLEvent.START_ELEMENT) &&
                        (elementName.equals(xmlStreamReader.getLocalName()))) {
                    result = xmlStreamReader.getElementText();
                    break;
                }
            }
 
            return result;
        }
        catch(XMLStreamException e) {
            throw new ProcessorException(
                  "Unable to parse the ACI response.", e);
        }
    }

}

The following example processes the response from the UserReadUserList action into a UserList JavaBean.

public class UserReadUserListProcessor extends AbstractStAXProcessor<UserList> {

    public UserReadUserListProcessor() {
        setErrorProcessor(new ErrorProcessor());
    }

    @Override
    public UserList process(final XMLStreamReader xmlStreamReader) {
        try {
            if(isErrorResponse(xmlStreamReader)) {
                processErrorResponse(xmlStreamReader);
            }

            final UserList userList = new UserList();

            while(xmlStreamReader.hasNext()) {
                final int eventType = xmlStreamReader.next();

                // We want the start elements...
                if(XMLEvent.START_ELEMENT == eventType) {
                    if("autn:user".equalsIgnoreCase(
                            xmlStreamReader.getLocalName())) {
                        userList.addUserName(xmlStreamReader.getElementText());
                    }
                    else if("autn:totalusers".equalsIgnoreCase(
                          xmlStreamReader.getLocalName())) {
                        userList.setTotalUsers(NumberUtils.toInt(
                              xmlStreamReader.getElementText()));
                    }
                    else if("autn:numusers".equalsIgnoreCase(
                          xmlStreamReader.getLocalName())) {
                        userList.setNumUsers(NumberUtils.toInt(
                              xmlStreamReader.getElementText()));
                    }
                }
            }

            return userList;
        }
        catch(XMLStreamException xmlse) {
            throw new ProcessorException(
                    "Unable to create a list of users.", xmlse);
        }
    }

}