import java.util.HashMap; import java.util.Map; import java.util.Vector; import lotus.domino.DateTime; import lotus.domino.Document; import lotus.domino.Item; import lotus.domino.NotesException; import com.ibm.ctp.NSFUtils; /** * @author Stephan H. Wissel st.wissel@sg.ibm.com * Compares 2 documents and * updates changes from the source document to the target document based * on item change dates. One way sync. Documents don't get saved - * that's job of the code calling this */ public class DocumentUpdater { private final Document docSource; private final Document docTarget; // We copy only field in the fieldsToCopy range private boolean importAllItems = true; // What fields to be copied from source to // target, key is the Target field private final Map<String, String> fieldsToCopy = new HashMap<String, String>(); /** * Default constructor that provides the two documents for a default * operation nothing else is needed, but optional fields to copy can be * introduced as parameters * * @param source * @param target */ public DocumentUpdater(final Document source, final Document target) { this.docSource = source; this.docTarget = target; } /** * @return all fields to be copied, defaults to all */ public Map<String, String> getFieldsToCopy() { return fieldsToCopy; } /** * @return Do we import all items or only selected ones? if selected ones * the item names can change */ public boolean isImportAllItems() { // If we don't have a field list, we must copy all documents if (this.fieldsToCopy == null || this.fieldsToCopy.isEmpty()) { return true; } return this.importAllItems; } /** * @param fieldsToCopy */ public void setFieldsToCopy(Map<String, String> fieldsToCopy) { this.fieldsToCopy.clear(); this.fieldsToCopy.putAll(fieldsToCopy); } /** * @param importAllItems */ public void setImportAllItems(boolean importAllItems) { this.importAllItems = importAllItems; } /** * Copies new data from the source document to the target document. If the * data has been updated, items are copied * * @return did the sync operation work (were updates processed) * @throws NotesException */ @SuppressWarnings("unchecked") public boolean syncDocuments() throws NotesException { // We can't sync NULL values if (docSource == null || docTarget == null) { return false; } boolean updated = false; // Ee check for the timestamp, we don't need to do // anything if the timestamp is still valid if (docTarget.hasItem(ITEM_TIMESTAMP_SRC)) { DateTime timeStamp = (DateTime) docTarget.getItemValueDateTimeArray(ITEM_TIMESTAMP_SRC).get(0); DateTime lastUpdate = docSource.getLastModified(); updated = updated || (lastUpdate.timeDifference(timeStamp) > 0); NSFUtils.shred(timeStamp, lastUpdate); // If the document hasn't changed we stop here! if (!updated) { return updated; } } // The names of all fields we processed, copied or not HashMap<String, String> processedItems = new HashMap<String, String>(); // Now all items need to be checked and copied Vector allItems = docSource.getItems(); // First through all source items for (Object itemObject : allItems) { Item curItem = (Item) itemObject; String itemName = curItem.getName(); if (this.isItemToBeImported(itemName)) { // When we copy all items we keep the name, otherwise the name // is in the fieldsToCopy Map String targetItemName = this.isImportAllItems() ? itemName : this.fieldsToCopy.get(itemName.toLowerCase()); // Note that we have processed it: processedItems.put(targetItemName, itemName); if (docTarget.hasItem(targetItemName)) { Item targetItem = docTarget.getFirstItem(targetItemName); if (targetItem.getLastModified().timeDifference(curItem.getLastModified()) > 0 && !targetItem.getText().equals(curItem.getText())) { updated = true; curItem.remove(); curItem.copyItemToDocument(docTarget, targetItemName); } NSFUtils.shred(targetItem); } else { updated = true; curItem.copyItemToDocument(docTarget, targetItemName); } } NSFUtils.shred(curItem); } // Now reverse process to work on the no longer needed target items allItems = docTarget.getItems(); for (Object itemObject : allItems) { Item curItem = (Item) itemObject; String itemName = curItem.getName(); if (!processedItems.containsKey(itemName) && !this.isReservedFieldName(itemName)) { // This item is no longer in the source // and we don't want/need it curItem.remove(); updated = true; } NSFUtils.shred(curItem); } if (updated) { docTarget.replaceItemValue(ITEM_TIMESTAMP_SRC, docSource.getLastModified()); docTarget.replaceItemValue(ITEM_EXT_UNID, docSource.getUniversalID()); // TODO: What is that caching mechanism? } return updated; } /** * Check if an item is on the "guest list" - if the guest list is empty then * all items are invited (of course not the local ones * * @param itemName * @return true if the item needs to be imported */ private boolean isItemToBeImported(String itemName) { // No internal or special field names if (this.isReservedFieldName(itemName)) { return false; } // If we have no item name map we presume we copy all items if (this.importAllItems || this.fieldsToCopy == null || this.fieldsToCopy.isEmpty()) { return true; } // Finally we check if it is in the list return this.fieldsToCopy.containsKey(itemName); } /** * Check for fields we don't want to copy * * @param itemName * @return true if is is a special field we don't import */ private boolean isReservedFieldName(String itemName) { return (itemName.equalsIgnoreCase(ITEM_FORM) || itemName.equalsIgnoreCase(ITEM_AUTHORS) || itemName.equalsIgnoreCase(ITEM_READERS) || itemName.toLowerCase().startsWith(ITEM_CTP_SFX) || itemName.toLowerCase().startsWith(ITEM_CTP_DATESFX) || itemName.startsWith("$")); } }