Information for RFID Vendors

Implementing RFID driver which accepts requests from Alma

Alma customers who would like to configure Alma to work with their RFID solutions follow the instructions here.

Alma out-of-the-box supports 3M, Bibliotheca and Nedap’s protocols. To allow for other vendors to integrate with Alma we added a third type: “Others.” When “Others” is configured in Alma, Alma initiates REST API calls to a defined URL. Details regarding the URLs of the requests and the structure of the input & output will follow.

NGINX as a proxy for adding CORS headers

We instruct our customers to install NGINX and configure it to proxy the requests from Alma to the RFID driver for the following reason: Alma sends the requests from a browser using JavaScript (AKA as AJAX requests). For security reasons browsers block AJAX requests from the site’s domain to other domains. A solution for this is for the ‘other domain’ to return a set of headers called CORS which signal the browser to allow it. Bibliotheca and 3M don’t return CORS headers so we solved it by adding another layer: NGINX accepts the requests, proxies them to the RFID driver, and when the response returns, sends it to Alma together with CORS headers.

To save our mutual customers the effort of installing NGINX and configuring it, we recommend that you add CORS headers to your response. See example in Java in the appendix below.

Testing using an Alma emulator

In order to test it without the need for a “real” Alma we created an “Alma simulator”: https://api-eu.hosted.exlibrisgroup.com/test/iframe/parent5

Choose http://sandbox01-na.alma.exlibrisgroup.com/view/general/rfid.html and click on ‘Show popup’.

Then select System: Other , and URL: Other – simulator on sandbox01-na

On the popup you can see a log by clicking anywhere on its background.

If you like, view the popup’s source-code – it is all in plain JavaScript.

Requests from Alma

For all response below make sure that you include Content-Type header with the value: application/xml

Error handling

For all services below, if any error has happened on the RFID reader side, an error message can be returned which will be displayed to the librarian.

Simply wrap the error message in <Message> which is inside <ExceptionDetail>

For example: <rfid><ExceptionDetail><Message>something went wrong</Message></ExceptionDetail></rfid>

Read items placed on the antenna

GET Url+"/getItems"

Sample output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rfid>
  <items>
    <!-- here we demo a situation when 2 items are placed on the reader -->
    <item>
      <!-- one with 2 parts and the other with just one -->
      <barcode>123</barcode>
      <!-- barcode of the first (multi-part) item -->
      <is_secure>true</is_secure>
      <!-- true = all parts have their security bit on -->
      <is_complete>true</is_complete>
      <!-- true = all parts are placed -->
      <total_num_of_parts>2</total_num_of_parts>
      <tags>
        <tag>
          <tag_id>123a</tag_id>
          <part_num>1</part_num>
          <material_type>DVD</material_type>
          <!-- can be mapped in Alma from any code to Alma codes -->
          <library>LIB1</library>
          <!-- can be mapped in Alma from any code to Alma codes -->
          <location>LOC1</location>
          <!-- can be mapped in Alma from any code to Alma codes -->
        </tag>
        <tag>
          <tag_id>123b</tag_id>
          <part_num>2</part_num>
          <material_type>DVD</material_type>
          <library>LIB1</library>
          <location>LOC1</location>
        </tag>
      </tags>
    </item>
    <item>
      <!-- second item which has just 1 part -->
      <barcode>456</barcode>
      <is_secure>false</is_secure>
      <!-- false = it has been checked out -->
      <is_complete>true</is_complete>
      <!-- for 1 part item it will always be true -->
      <total_num_of_parts>1</total_num_of_parts>
      <tags>
        <tag>
          <tag_id>456a</tag_id>
          <part_num>1</part_num>
          <material_type>BOOK</material_type>
          <library>LIB1</library>
          <location>LOC2</location>
        </tag>
      </tags>
    </item>
  </items>
</rfid>

 

Set security bit to on/off

POST Url+"/setSecurity"

Sample payload:

<rfid>
  <barcode>thebarcode</barcode>
  <is_secure>true</is_secure>
</rfid>

 

Sample output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rfid>
  <success>true</success>
</rfid>

Update tag placed on the antenna

POST Url+"/itemUpdate"

Sample payload:

<rfid>
  <items>
    <item>
      <barcode>31345678901234</barcode>
      <is_secure>false</is_secure>
      <tags>
        <tag>
          <total_num_of_parts>1</total_num_of_parts>
          <part_num>1</part_num>
          <material_type>64</material_type>
          <library></library>
          <location></location>
        </tag>
      </tags>
    </item>
  </items>
</rfid>

 

Sample output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rfid>
  <success>true</success>
</rfid>

Note that the payload includes <is_secure>false</is_secure> – Alma defaults to: true (for new items in the library it can be assumed that the security bit should be turned on, as the item isn’t not checked-out currently.).   However before sending itemUpdate we call getItems and check the output. If we found that the output includes <is_secure> tag we use its value for the value in the input for itemUpdate.

Reader IP Address

The regular topology would be a reader connected to each PC, listening on localhost.

Newer topology includes readers connect to the network, and each librarian configures the IP address of the reader she would like to use. (Note: in Alma it is configured at the circulation-desk level). Requests from the popup are sent to a  centralized server and from their routed to the relevant reader. For such a topology the requests from the popup will include the configured reader’s IP address:

  • GET requests – in a query parameter: reader_ip=1.2.3.4
  • POST requests – in the payload: <reader_ip>1.2.3.4</reader_ip>

Appendix – Java stub methods simulating the server side of the RFID driver

Note the CORS headers added for both OPTION and GET/POST.

// /RFIDsimulator/getItems
/*
* Alma doesn't do anything with the material_type, library, location. The
* most important is the barcode. is_complete - when false Alma displays a
* warning.
*/
@Path("/RFIDsimulator/getItems")
@OPTIONS
@Override
public String getDetectedItemsOptions(@Context HttpServletResponse resp) throws Exception {
  addHeadersForCORS(resp);
  return "";
}

@Path("/RFIDsimulator/getItems")
@GET
@Override
public String getDetectedItems(@Context HttpServletResponse resp) throws Exception {
  logger.info("RFID simulator API: getItems");
  String out = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n<rfid>\r\n\t<items> <!-- here we demo a situation when 2 items are placed on the reader -->\r\n\t\t<item> <!-- one with 2 parts and the other with just one -->\r\n\t\t\t<barcode>123</barcode> <!-- barcode of the first (multi-part) item -->\r\n\t\t\t<is_secure>true</is_secure> <!-- true = all parts have their security bit on -->\r\n\t\t\t<is_complete>true</is_complete> <!-- true = all parts are placed -->\r\n\t\t\t<total_num_of_parts>2</total_num_of_parts>\r\n\t\t\t<tags>\r\n\t\t\t\t<tag>\r\n\t\t\t\t\t<tag_id>123a</tag_id>\r\n\t\t\t\t\t<part_num>1</part_num>\r\n\t\t\t\t\t<material_type>DVD</material_type> <!-- can be mapped in Alma from any code to Alma codes -->\r\n\t\t\t\t\t<library>LIB1</library> <!-- can be mapped in Alma from any code to Alma codes -->\r\n\t\t\t\t\t<location>LOC1</location> <!-- can be mapped in Alma from any code to Alma codes -->\r\n\t\t\t\t</tag>\r\n\t\t\t\t<tag>\r\n\t\t\t\t\t<tag_id>123b</tag_id>\r\n\t\t\t\t\t<part_num>2</part_num>\r\n\t\t\t\t\t<material_type>DVD</material_type>\r\n\t\t\t\t\t<library>LIB1</library>\r\n\t\t\t\t\t<location>LOC1</location>\r\n\t\t\t\t</tag>\r\n\t\t\t</tags>\r\n\t\t</item>\r\n\t\t<item> <!-- second item which has just 1 part -->\r\n\t\t\t<barcode>456</barcode>\r\n\t\t\t<is_secure>false</is_secure> <!-- false = it has been checked out -->\r\n\t\t\t<is_complete>true</is_complete> <!-- for 1 part item it will always be true -->\r\n\t\t\t<total_num_of_parts>1</total_num_of_parts>\r\n\t\t\t<tags>\r\n\t\t\t\t<tag>\r\n\t\t\t\t\t<tag_id>456a</tag_id>\r\n\t\t\t\t\t<part_num>1</part_num>\r\n\t\t\t\t\t<material_type>BOOK</material_type>\r\n\t\t\t\t\t<library>LIB1</library> \r\n\t\t\t\t\t<location>LOC2</location>\r\n\t\t\t\t</tag>\r\n\t\t\t</tags>\r\n\t\t</item>\r\n\t</items>\r\n</rfid>";
  addHeadersForCORS(resp);
  return out;
}

// /RFIDsimulator/setSecurity
/*-
Changing RFID tag security bit. Expected input:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rfid>
  <!-- Alma sends the request by barcode. The device is expected to identify all tags on the antenna related to this barcode and update them all. -->
  <barcode>123</barcode>
  <is_secure>true</is_secure>
</rfid>
*/
@Path("/RFIDsimulator/setSecurity")
@OPTIONS
@Override
public String setSecurityOptions(@Context HttpServletResponse resp) throws Exception {
  addHeadersForCORS(resp);
  return "";
}

@Path("/RFIDsimulator/setSecurity")
@POST
@Override
public String setSecurity(String in, @Context HttpServletResponse resp) throws Exception {
  logger.info("RFID simulator API: setSecurity. Input: " + in);
  String out = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n<rfid>\r\n\t<success>true</success>\r\n</rfid>";
  addHeadersForCORS(resp);
  return out;
}

/*-
Updating RFID tag. Expected input:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rfid>
  <items> <!-- same structure as in getItems API, but only one tag is expected -->
    <item>
      <barcode>123</barcode>
      <!-- the security state can be changed here too, but Alma will not try to change it.
      i.e. Alma will first read the existing state using getItems API, and send the state it received to this API.
      If it can't read (e.g. new tag) it will default to on (true). -->
      <is_secure>true</is_secure>
      <tags>
        <tag>
          <total_num_of_parts>2</total_num_of_parts>
          <part_num>1</part_num>
          <material_type>DVD</material_type>
          <library>LIB1</library>
          <location>LOC1</location>
        </tag>
      </tags>
    </item>
  </items>
</rfid>
*/
// /RFIDsimulator/itemUpdate
@Path("/RFIDsimulator/itemUpdate")
@OPTIONS
@Override
public String itemUpdateOptions(@Context HttpServletResponse resp) throws Exception {
  addHeadersForCORS(resp);
  return "";
}

@Path("/RFIDsimulator/itemUpdate")
@POST
@Override
public String itemUpdate(String in, @Context HttpServletResponse resp) throws Exception {
  logger.info("RFID simulator API: itemUpdate. Input: " + in);
  String out = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n<rfid>\r\n\t<success>true</success>\r\n</rfid>";

  addHeadersForCORS(resp);
  return out;
}

private void addHeadersForCORS(HttpServletResponse resp) {
  resp.setHeader("Access-Control-Allow-Origin", "*");
  resp.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
  resp.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Content-Type");
}