Recently I’ve been working with the Name Picker where I was using the Name Picker to select values that would then be used for Readers and Authors fields. I noticed that although it picks from address books, the names are stored by default in abbreviated format, e.g. Paul Withers/MyCompany rather than canonical format, e.g. CN=Paul Withers/O=MyCompany. This prompted a discussion with some other XPagers, to check I wasn’t going mad. There was some discussion about whether or not Readers and Authors fields needed to be in hierarchical format. I had a feeling I had come across a situation somewhere that had taught me it was important to store names in hierarchical format, but I couldn’t remember where. Fortunately, Karsten Lehmann found a developerWorks article on understanding Readers fields. Note particularly the section entitled Formatting Names:
Reader Names and Author fields must have individual names in either flat or canonical format, but not abbreviated format. For example, CN=Raphael Savir/OU=Boston/O=LSDevelopment is fine, and Raphael Savir is also fine, but Raphael Savir/Boston/LSDevelopment does not work.
This can be confusing because a Reader Names or Author field displays canonical data in an abbreviated format to make it easier to read. When you open a document and read the names in such a field, you see Raphael Savir/Boston/LSDevelopment. But if you look at the underlying data using document properties, you see that it’s really stored canonically as CN=Raphael Savir/OU=Boston/O=LSDevelopment (see figure 3). This ability to clean up the display of canonical names is true of all Names fields, but not true of plain text fields.
If you put a flat name into a Reader Names or Author field, it works only as long as the server has the same hierarchy as the user. For example, suppose that your company merges with another company and that you have data replicating between servers of different hierarchies. Let’s call one Apps/NorthEast/LSDevelopment, and the other HQ/ACME. If our user attempts to access the data in each replica, and if his data is in Reader Names fields using his flat name, then he is only successful on the Apps/NorthEast/LSDevelopment server because he shares hierarchy with only that server (the /O= portion of his name). He may be cross-certified to access the other server, HQ/ACME, but the Reader Names and Author fields do not grant him access to the data itself.
So the abbreviated format returned by the Name Picker should be converted to canonical format if used for Readers fields. Even if a server doesn’t use OUs currently, it’s dangerous to assume it never will. And it’s also advisable to follow best practice, because if you end up developing an application for a business that uses OUs, canonical names will be required and your standard code will be automatically still work.
For Readers and Authors fields in XPages forcing the right name format is one requirement, but the other is setting the isReaders and/or isAuthors property on the Notes Item. XPages doesn’t have a concept of a Names, Readers or Authors control. It may seem a failing, but when you consider that the controls are bound to the DominoDocument datasource (the XPages equivalent of a NotesUIDocument in LotusScript), it makes sense. That is because the DominoDocument datasource, like the NotesUIDocument object, only has methods to set a field value – DominoDocument.replaceItemValue() and NotesUIDocument.FieldSetText.
With a Notes form, the Readers or Authors or Names property is managed by the field data type. That is applied during the process of writing the values into Notes Items. Also at that point the contents of the field is automatically converted from the abbreviated format that users see to the canonical format that is stored. And this is one option for setting both the data type and the canonical format: to set the computeWithForm property on the DominoDocument datasource to “onsave”. This would be the recommended approach if using the Save Document or Save Data Source simple action.
The field also does some magic of displaying in abbreviated format – a “converter” as it were. Indeed that’s exactly what a converter does – it converts the underlying Notes Item value to a specific String format required by HTML. So that is another method of converting the abbreviated format to canonical format. But it’s not an option I would favour because you still need to set the Readers or Authors or Names property elsewhere. My preference is to perform both actions in the same place.
So I have a save method to perform the business logic, in my case in Java. DominoDocument.getDocument(true) allows code to be run on the back-end Document object, passing any values set on the DominoDocument datasource. So I convert to canonical format and set Readers or Authors or Names property in that save method.
At this point, it’s relevant to mention the methods in DominoUtils in the OpenNTF Domino API, which is now also available (for 9.0.1) in OpenNTF Essentials. There are a number of methods for name manipulation in DominoUtils, things like toCommonName, toAbbreviatedName etc. These will actually run quicker than converting to a Name object and calling the relevant methods because they use regex to strip out the relevant elements. But that means they also need a canonical format as a starting point, unlike the various Name methods.
In addition, there is also DominoUtils.isHierarchicalName(), which is also very useful. So the code I use (with the OpenNTF Domino API) to set as a Readers field and convert all elements to canonical format is:
Item readersItem = doc.getFirstItem("Readers");
readersItem.setReaders(true);
ArrayList<String> newNames = new ArrayList<String>();
for (Object name: readersItem.getValues()) {
if (!DominoUtils.isHierarchicalName(name.toString())) {
Name uName = currSess.createName(name.toString());
newNames.add(uName.getCanonical());
} else {
newNames.add(name.toString());
}
}
doc.replaceItemValue("Readers",newNames);
Note also one of the nice things with the OpenNTF Domino API: you don’t need a Vector to save into a multi-value text field. You can pass an ArrayList. The replaceItemValue() method (or you can use the doc.put()) automatically converts it as required.