Ready to Learn?Ex Libris products all provide open APIs

Tech Blog

 

DNX update by a repository task plugin

Timothee Lecaudey on August 30th, 2017

This post is intended as a kind of tutorial on creating repository task plugins to modify DNX metadata in batch.

This plugin was created to correct IEs where a Handle identifier was created in the wrong place: it was added to the internalIdentifier section of the IE DNX, but should have been in fact be in the objectIdentifier section. More than its original purpose, the interest is to see how the repository task plugin can retrieve and update the DNX metadata in different ways: reading, deleting, adding.

Source Code for this plugin

Please refer to the Rosetta.DnxMoveHandle repository on the Ex Libris GitHub

References

For this plugin you will need to refer mainly to the IEEditor Interface's javadoc. This is where the classes and methods used to access and update the IE's metadata are described.

Overview - the execute() method

Our plugin has three main steps

  1. Extract the DNX contents from the IE
  2. Retrieve the internalIdentifier of type "handle" or "Handle" and store its value in a String object
  3. Update the DNX. This task has three sub-steps:

     - Remove the internalIdentifier of type "handle" or "Handle" from the DNX
     - Add a new objectIdentifier of type "HANDLE" to the DNX
     - Update the IE

Tasks 1-3 are executed by the execute() method, which is the main method of our program and is called automatically by Rosetta when running the plugin. Basically, everything that we write here will constitute the main functionality of our plugin. The execute() method calls secondary methods, getHandleValue and updateDnx, which execute some specific tasks.

public TaskResults execute(IEEditor ieEditor, Map<string> initParams, TaskResults taskResults) {
        log.info("Executing DnxMoveHandle for " + ieEditor.getIEPid());
        init(initParams);
        DnxDocumentHelper ieDnxH = null;
        //open DNX
        try {
                ieDnxH = ieEditor.getDnxHelperForIE();
        } catch (Exception e) {
                taskResults.addResult(ieEditor.getIEPid(), null, false, FAIL_GET_DNX);
                return taskResults;
        }
        //get Handle value
        String handleValue = getHandleValue(ieDnxH);
        //update DNX
        if (handleValue == null) {
                taskResults.addResult(ieEditor.getIEPid(), null, false, FAIL_FIND_VALUE);
        } else {
                log.info("Handle for " + ieEditor.getIEPid() + " is " + handleValue);
                log.info("Starting DNX update task...");
                try {
                        updateDnx(ieDnxH, ieEditor, handleValue);
                } catch (Exception e) {
                        taskResults.addResult(ieEditor.getIEPid(), null, false, e.getMessage());
                }
        }
        return taskResults;
}

Let's examine the 3 main steps of the execute() method one by one:

1. Open the DNX for editing

The execute() method takes an IEEditor object as parameter. The IEEditor is an interface for retrieving and editing metadata in the IE.

We first create a new DnxDocumentHelper object, and use the IEEditor's getDnxHelperForIE() function to get its contents from the IE.

DnxDocumentHelper ieDnxH = null;
//get Dnx from IE
try {
        ieDnxH = ieEditor.getDnxHelperForIE();
} catch (Exception e) {
        taskResults.addResult(ieEditor.getIEPid(), null, false, FAIL_GET_DNX);
        return taskResults;
}

The DnxDocumentHelper is not the DNX itself but an object containing the DnxDocument and facilitating the manipulation of the DNX's different sections via specialized methods. For example, there are methods to access the internalIdentifier section, methods for the Producer section, etc. It makes it easy to access only the DNX sections that we want to read or update, and not the whole DNX metadata, which is usually quite long and complex. There is also no need to use XML: we only have to use the methods provided by the DnxDocumentHelper object.

In case of problems when reading the DNX, the try...catch clause will catch any error messages returned by getDnxHelperForIE() and add them to the task's results. In practice, this interrupts the execution of the task on the current IE and adds an error in the log and in the UI's exceptions list.

2. Retrieve the Handle value

To extract the Handle value from the internalIdentifier section of the DNX, we define a new method called getHandleValue(). This method takes the DnxDocumentHelper extracted in 1. as parameter.

Here is a sample internalIdentifier section of a DNX document represented in XML:

<section id="internalIdentifier"> 
<record> 
  <key id="internalIdentifierType">handle</key> 
  <key id="internalIdentifierValue">101381/404295</key> 
</record> 
<record> 
  <key id="internalIdentifierType">SIPID</key> 
  <key id="internalIdentifierValue">501</key> 
</record> 
<record> 
  <key id="internalIdentifierType">PID</key> 
  <key id="internalIdentifierValue">IE5066</key> 
</record> 
<record> 
  <key id="internalIdentifierType">DepositSetID</key> 
  <key id="internalIdentifierValue">2523</key> 
</record> 
</section>

As you can see, there are usually multiple records in this section, so the DnxDocumentHelper method getInternalIdentifiers() is going to return a list of InternalIdentifier objects (one for each ID - four in our example).

We go over the list with a for loop (or an Iterator) and copy the value when the type is "handle" or "Handle", using the InternalIdentifier methods getInternalIdentifierType() and getInternalIdentifierValue(). This value is stored in a String object and returned.

String handleValue = null;
List<InternalIdentifier> intIdList = ieDnxH.getInternalIdentifiers(); 
for (InternalIdentifier intId : intIdList) { 
        if (intId.getInternalIdentifierType().matches("[Hh]andle")) { 
                handleValue = intId.getInternalIdentifierValue(); 
        } 
}
return handleValue;

3. Update the DNX

To update the DNX, we define a new method called updateDnx(). This method takes three parameters: the DnxDocumentHelper, the IEEditor and the handleValue String.

- Check the objectIdentifier section

Before we update the objectIdentifier with the Handle value we just copied from the internalIdentifier section, it's a good idea to check if the objectIdentifier does not already contain a Handle. If it does, we will return an exception and go to the next IE.

boolean existHandle = false;
List<objectidentifier> objIdList = ieDnxH.getObjectIdentifiers();
for (ObjectIdentifier objId : objIdList) {
        if (objId.getObjectIdentifierType().equals("HANDLE")) {
                existHandle = true;
        }
}

This works exactly the same as reading the internalIdentifier section, but this time we do not copy any value, just return "existHandle = true" if the Handle already exists.

- Remove the InternalIdentifier:

Same as in 2., we first get the list of InternalIdentifier objects in the DNX using getInternalIdentifiers(). We then create a new list, by selecting only the IDs that are not of type handle, and that we want to keep. This list is then passed to the setInternalIdentifiers() method, which updates the DnxDocumentHelper.

Important: at this point we have not yet updated the IE. What we have is an updated DnxDocumentHelper, containing all original internalIdentifiers except Handle, and existing only in our program. We will need to use the IEEditor again later to insert the updated DNX into the IE.

//remove InternalIdentifier of type handle or Handle from DnxDocumentHelper
List<internalidentifier> intIdList = ieDnxH.getInternalIdentifiers();
List<internalidentifier> updatedIntIdList = new ArrayList<internalidentifier>();
for (InternalIdentifier intId : intIdList) {
        if (!intId.getInternalIdentifierType().matches("[Hh]andle")) {
                updatedIntIdList.add(intId);
        }
}
ieDnxH.setInternalIdentifiers(updatedIntIdList);
log.info("Successfully deleted the handle from InternalIdentifier section");

- Add the ObjectIdentifier

Now, we need to create a new ObjectIdentifier object to store the Handle, if it does not already exist. The constructor allows us to create it at once, with a type and value.

It can now be added to the list of ObjectIdentifier objects (that we got from the DnxDocumentHelper when we checked if the handle already existed), using the List's add() method. Finally we can update the DnxDocumentHelper, replacing the existing objectIdentifier section:

//add ObjectIdentifier of type HANDLE to DnxDocumentHelper
ObjectIdentifier newObjId = ieDnxH.new ObjectIdentifier("HANDLE",handleValue);
objIdList.add(newObjId);
ieDnxH.setObjectIdentifiers(objIdList);

- Update the IE

The last step of our method is simply to insert the updated DnxDocumentHelper into the IE, using the IEEditor:

//update DnxDocumentHelper in IEEditor
ieEditor.setDnxForIE(ieDnxH);
log.info("Successfully copied the Handle to ObjectIdentifier section");

About the other methods

The init() method retrieves the plugin's parameters from the Task Chain it is used in. In our case, there are no parameters (see the metadata form). Please refer to the DcReplacePlugin for an example of RepositoryTask using parameters.

private void init(Map<String, String> initParams) { 

}

The isReadOnly() method tells the plugin if it should update the IE or run in test mode. We set it to false to make it update the IE.

public boolean isReadOnly() { 
        return false; 
}

The main() method is mandatory, but does nothing in our case.

public static void main(String[] args) { 

}

The metadata file

The metadata file of the plugin is stored under the src/PLUGIN-INF directory. It is quite simple since no initParameters have to be defined for this project.

Deployment

To deploy the plugin the first time, copy the JAR file to $op_dir/plugins/custom. In Rosetta, go to Administer the system > Plugin Management > Custom tab. Here you can add the plugin to the list.

If you change the code later, increment the version number in the metadata file, re-package the JAR, upload it to the server and click on the Upgrade button in Rosetta.

Prerequisites: Building a new plugin project from scratch in Eclipse

1. Download and Install Eclipse
2. Download and Install Java Development Kit (1.8 for Rosetta sdk 5.x.x)
3. Download the SDK's and javadoc's jar files and from /exlibris/dps/d4_1/system.dir/dps-sdk-{version}/lib
4. Create a new Java Project in Eclipse
5. Configure the project libraries
     - Import the SDK files and import them under a directory called /lib
     - In the Project's Properties> Java Build Path, add the SDK jar file (not the javadoc) to the libraries. This adds a folder "Referenced Libraries" to the project.
     - In "Referenced Libraries", open dps-sdk.5.x.x.jar's Properties > Javadoc location. Specify the location of the javadoc (under the /lib folder).
6. Create the main java file:
     - Add a new Package under /src called "com.exlibris.rosetta.plugins.repositoryTask"
     - Add a new .java file in the package
7. Create the metadata file
     - Add a new Folder under /src called "PLUGIN-INF"
     - Add a new .xml file named "metadata_.xml"
8. Configure the Ant builder (Ant is the program that will compile and package all the required files into the plugin's JAR file)
     - Create a file "build.xml" and a file "build.properties" at the root of the project (copy and update their respective contents from GitHub)
     - In the Project's Properties > Builders, add a new builder called Ant. Point it to the build.xml file and to the root of your project. Check "Refresh Sources upon Completion".
     - In Window > Preferences > Ant > Runtime, click on Global Entries, then Add External Jars and add the location of the tools.jar file which is under /lib.

You should be able to run your program or do a Project > Build All. Ant will place the plugin's jar file under /target. This is the file that you will copy to Rosetta.