/*
* Copyright 2013
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package org.openntf.domino.helpers;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.openntf.domino.Database;
import org.openntf.domino.Database.ModifiedDocClass;
import org.openntf.domino.DateTime;
import org.openntf.domino.Document;
import org.openntf.domino.DocumentCollection;
import org.openntf.domino.Item;
import org.openntf.domino.Session;
import org.openntf.domino.View;
import org.openntf.domino.transactions.DatabaseTransaction;
/**
* DocumentSyncHelper class
*
* This class provides a quick and easy way to sync fields or formulas from a DocumentCollection to related documents, e.g. from Companies
* to Contacts for those companies, or States to Contacts for those states.
*
* <ol>
* <li>Create a map of updates to make to related documents
* <ul>
* <li>Key is Item to pull from source document or formula to be processed against source document</li>
* <li>Value is Item name to wrote to on target documents
* <li>
* </ul>
* </li>
* <li>Create a new instance of DocumentSyncHelper
* <li>
* <li>Either in constructor or separately, define the strategy to apply when updating target documents</li>
* <li>Either in constructor or separately, load in the map</li>
* <li>Either in constructor or separately, define target server</li>
* <li>Either in constructor or separately, define target database</li>
* <li>Either in constructor or separately, define target view name</li>
* <li>Either in constructor or separately, define field in source to use as a key for target view</li>
* <li>Get a DocumentCollection of source document(s)</li>
* <li>Pass to DocumentSyncHelper.process method</li>
* </ol>
*
* Example:<br/>
* <code>
* Database currDb = XSPUtil.getCurrentDatabase();
* java.util.Map<Object, String> syncMap = new java.util.HashMap<Object, String>();
* syncMap.put("Key", "State");
* syncMap.put("Name", "StateName");
* syncMap.put("@Now", "LastSync");
* DocumentSyncHelper helper = new DocumentSyncHelper(DocumentSyncHelper.Strategy.CREATE_AND_REPLACE, syncMap,
* currDb.getServer(), currDb.getFilePath(), "AllContactsByState", "Key");
* View states = currDb.getView("AllStates");
* DocumentCollection sourceCollection = states.getAllDocuments();
* helper.process(sourceCollection);
* </code>
*
* Alternatively, sync settings can be held in a control document. Use a Map<Controls, String> to map DocumentSyncHelper properties to Item
* names on the control document, e.g.
*
* Map has key Controls.TARGET_SERVER=ServerName will look for an Item called ServerName on the control document to retrieve the server name
* to use in the DocumentSyncHelper
*/
public class DocumentSyncHelper {
/** The Constant log_. */
@SuppressWarnings("unused")
private static final Logger log_ = Logger.getLogger(DocumentSyncHelper.class.getName());
/**
* The Enum Strategy.
*
* Strategy to apply when updating target documents
*
* @since org.openntf.domino 1.0.0
*/
public static enum Strategy {
/** The replace if newer. */
REPLACE_IF_NEWER,
/** The create and replace. */
CREATE_AND_REPLACE,
/** The replace only. */
REPLACE_ONLY
}
/**
* The TransactionRule enum.
*
* The strategy to apply transactions
*
* @since org.openntf.domino 1.0.0
*/
public static enum TransactionRule {
NO_TRANSACTION, COMMIT_EVERY_SOURCE, COMMIT_AT_END
}
/**
* The Enum Controls.
*
* Provides standard access to map properties of the DocumentSyncHelper to Item names in a control document from the control Map
*
* @since org.openntf.domino 1.0.0
*/
public static enum Controls {
/** The target server. */
TARGET_SERVER,
/** The target filepath. */
TARGET_FILEPATH,
/** The target lookup view. */
TARGET_LOOKUP_VIEW,
/** The source key formula. */
SOURCE_KEY_FORMULA,
/** The strategy. */
STRATEGY,
/** The sync source field. */
SYNC_SOURCE_FIELD,
/** The sync target field. */
SYNC_TARGET_FIELD;
}
/** The target server_. */
private String targetServer_;
/** The target filepath_. */
private String targetFilepath_;
/** The target lookup view_. */
private String targetLookupView_;
/** The source key formula_. */
// private String sourceKeyFormula_;
private Formula sourceKeyFormula_;
/** The strategy_. */
private Strategy strategy_;
private TransactionRule transactionRule_;
/** The sync map_. */
private Map<Formula, String> syncMap_;
/**
* Instantiates a new document sync helper.
*
* @since org.openntf.domino 1.0.0
*/
public DocumentSyncHelper() {
// TODO allow for constructor arguments to configure
}
/**
* Instantiates a new DocumentSyncHelper with a Document of sync settings and a Map of field names for each setting in the control doc
*
* @param controlDoc
* Document with Items for relevant settings for the DocumentSyncHelper
* @param controlMap
* Map<Controls, String> of Item names to use to retrieve DocumentSyncHelper settings from the control doc
* @since org.openntf.domino 1.0.0
*/
public DocumentSyncHelper(final Document controlDoc, Map<Controls, String> controlMap) {
if (controlMap == null)
controlMap = new HashMap<Controls, String>();
if (controlMap.containsKey(Controls.TARGET_SERVER)) {
setTargetServer(controlDoc.getItemValueString(controlMap.get(Controls.TARGET_SERVER)));
} else if (controlDoc.hasItem(Controls.TARGET_SERVER.toString())) {
setTargetServer(controlDoc.getItemValueString(Controls.TARGET_SERVER.toString()));
} else {
setTargetServer(controlDoc.getParentDatabase().getServer());
}
if (controlMap.containsKey(Controls.TARGET_FILEPATH)) {
setTargetFilepath(controlDoc.getItemValueString(controlMap.get(Controls.TARGET_FILEPATH)));
} else if (controlDoc.hasItem(Controls.TARGET_FILEPATH.toString())) {
setTargetFilepath(controlDoc.getItemValueString(Controls.TARGET_FILEPATH.toString()));
} else {
setTargetFilepath(controlDoc.getParentDatabase().getFilePath());
}
if (controlMap.containsKey(Controls.TARGET_LOOKUP_VIEW)) {
setTargetLookupView(controlDoc.getItemValueString(controlMap.get(Controls.TARGET_LOOKUP_VIEW)));
} else if (controlDoc.hasItem(Controls.TARGET_LOOKUP_VIEW.toString())) {
setTargetLookupView(controlDoc.getItemValueString(Controls.TARGET_LOOKUP_VIEW.toString()));
} else {
setTargetLookupView("TARGET_LOOKUP_VIEW");
}
if (controlMap.containsKey(Controls.SOURCE_KEY_FORMULA)) {
setSourceKeyFormula(controlDoc.getItemValueString(controlMap.get(Controls.SOURCE_KEY_FORMULA)));
} else if (controlDoc.hasItem(Controls.SOURCE_KEY_FORMULA.toString())) {
setSourceKeyFormula(controlDoc.getItemValueString(Controls.SOURCE_KEY_FORMULA.toString()));
} else {
setSourceKeyFormula("SOURCE_KEY_FORMULA"); // this would be the name of the field on the source document used for the lookup
}
if (controlMap.containsKey(Controls.STRATEGY)) {
setStrategy(Strategy.valueOf(controlDoc.getItemValueString(controlMap.get(Controls.STRATEGY))));
} else if (controlDoc.hasItem(Controls.STRATEGY.toString())) {
setStrategy(Strategy.valueOf(controlDoc.getItemValueString(Controls.STRATEGY.toString())));
} else {
setStrategy(Strategy.CREATE_AND_REPLACE);
}
Map<Object, String> syncMap = new HashMap<Object, String>();
if (controlMap.containsKey(Controls.SYNC_SOURCE_FIELD) && controlMap.containsKey(Controls.SYNC_TARGET_FIELD)) {
java.util.Vector<Object> keyVec = controlDoc.getItemValue(controlMap.get(Controls.SYNC_SOURCE_FIELD));
java.util.Vector<Object> valueVec = controlDoc.getItemValue(controlMap.get(Controls.SYNC_TARGET_FIELD));
int i = 0;
for (Object key : keyVec) {
Formula Fkey = new Formula();
Fkey.setExpression((String) key);
syncMap.put(Fkey, (String) valueVec.get(i));
i++;
}
} else if (controlDoc.hasItem(Controls.SYNC_SOURCE_FIELD.toString()) && controlDoc.hasItem(Controls.SYNC_TARGET_FIELD.toString())) {
java.util.Vector<Object> keyVec = controlDoc.getItemValue(Controls.SYNC_SOURCE_FIELD.toString());
java.util.Vector<Object> valueVec = controlDoc.getItemValue(Controls.SYNC_TARGET_FIELD.toString());
int i = 0;
for (Object key : keyVec) {
Formula Fkey = new Formula();
Fkey.setExpression((String) key);
syncMap.put(Fkey, (String) valueVec.get(i));
i++;
}
} else {
// TODO some default sync mappping, perhaps dynamic
}
setSyncMap(syncMap);
}
/**
* Instantiates a new document sync helper passing in a sync map
*
* @param strategy
* the strategy
* @param syncMap
* the sync map
* @param args
* the args, up to four, each in order is:
* <ol>
* <li>target server</li>
* <li>target filepath</li>
* <li>target lookup view</li>
* <li>formula to apply to each source document to get key for target documents. E.g. "ContactID" = use ContactID field of
* source document as key value to apply to target lookup view to get the collection to update
* </ol>
* @since org.openntf.domino 1.0.0
*/
public DocumentSyncHelper(final Strategy strategy, final Map<Object, String> syncMap, final String... args) {
setStrategy(strategy);
setSyncMap(syncMap);
if (args.length >= 1) {
setTargetServer(args[0]);
}
if (args.length >= 2) {
setTargetFilepath(args[1]);
}
if (args.length >= 3) {
setTargetLookupView(args[2]);
}
if (args.length >= 4) {
setSourceKeyFormula(args[3]);
}
}
/**
* Extended method to process, allowing the developer to define to only process source documents modified since a given Java date
*
* @param sourceDb
* Database source documents are in
* @param sinceDate
* Date since when documents should have been modified
* @since org.openntf.domino 1.0.0
*/
public void processSince(final Database sourceDb, final Date sinceDate) {
DateTime dt = sourceDb.getAncestorSession().createDateTime(sinceDate);
DocumentCollection sourceCollection = sourceDb.getModifiedDocuments(dt, ModifiedDocClass.DATA);
process(sourceCollection);
}
/**
* Extended method to process, allowing the developer to define to only process source documents modified since a given Java date
*
* @param sourceDb
* Database source documents are in
* @param sinceDate
* Date since when documents should have been modified
* @param formName
* String form name to restrict DocumentCollection to
* @since org.openntf.domino 1.0.0
*/
public void processSince(final Database sourceDb, final Date sinceDate, final String formName) {
DateTime dt = sourceDb.getAncestorSession().createDateTime(sinceDate);
DocumentCollection sourceCollection = sourceDb.getModifiedDocuments(dt, ModifiedDocClass.DATA);
sourceCollection.FTSearch("[Form] = \"" + formName + "\"");
process(sourceCollection);
}
/**
* Process a specific DocumentCollection.
*
* WARNING: Does not currently check that all properties of the SyncHelper have been set up
*
* @param coll
* DocumentCollection of source documents
* @since org.openntf.domino 1.0.0
*/
public void process(final DocumentCollection coll) {
// TODO Check to make sure properties are all set up before running
Session session = coll.getAncestorSession();
Database targetDb = session.getDatabase(getTargetServer(), getTargetFilepath());
View targetView = targetDb.getView(getTargetLookupView());
Strategy strategy = getStrategy();
DatabaseTransaction txn = null;
if (getTransactionRule() == TransactionRule.COMMIT_AT_END) {
txn = targetDb.startTransaction();
}
for (Document source : coll) {
if (getTransactionRule() == TransactionRule.COMMIT_EVERY_SOURCE) {
txn = targetDb.startTransaction();
}
DateTime sourceLastMod = source.getLastModified();
// Object lookupKey = Factory.wrappedEvaluate(session, getSourceKeyFormula(), source);
Object lookupKey = getSourceKeyFormula().getValue(source);
DocumentCollection targetColl = targetView.getAllDocumentsByKey(lookupKey, true);
for (Document target : targetColl) {
// boolean targetDirty = false;
for (Map.Entry<Formula, String> entry : getSyncMap().entrySet()) {
String targetItemName = entry.getValue();
java.util.Vector<?> sourceValue = entry.getKey().getValue(source);
// Factory.wrappedEvaluate(session, entry.getKey(), source);
if (strategy == Strategy.CREATE_AND_REPLACE) {
target.replaceItemValue(targetItemName, sourceValue);
// targetDirty = true;
} else {
Item targetItem = target.getFirstItem(targetItemName);
if (strategy == Strategy.REPLACE_IF_NEWER) {
DateTime itemLastMod = targetItem.getLastModified();
if (sourceLastMod.isAfter(itemLastMod)) {
targetItem.setValues(sourceValue);
// targetDirty = true;
}
} else if (strategy == Strategy.REPLACE_ONLY) {
if (targetItem != null) {
targetItem.setValues(sourceValue);
// targetDirty = true;
}
}
}
}
if (getTransactionRule() == TransactionRule.NO_TRANSACTION || txn == null) {
target.save();
}
}
if (getTransactionRule() == TransactionRule.COMMIT_EVERY_SOURCE && txn != null) {
txn.commit();
txn = null;
}
}
if (getTransactionRule() == TransactionRule.COMMIT_AT_END && txn != null) {
txn.commit();
txn = null;
}
}
/**
* Sets the target View (and so also Database and Server) from which to retrieve documents
*
* @param view
* View to find documents to update
* @since org.openntf.domino 1.0.0
*/
public void setTargetView(final org.openntf.domino.View view) {
setTargetLookupView(view.getName());
setTargetDatabase(view.getAncestorDatabase());
}
/**
* Sets the target Database (and so also Server) from which to retrieve documents
*
* @param db
* Database to find documents to update
* @since org.openntf.domino 1.0.0
*/
public void setTargetDatabase(final Database db) {
setTargetServer(db.getServer());
setTargetFilepath(db.getFilePath());
}
/**
* Sets the target Database (and so also Server) and View from which to retrieve documents
*
* @param db
* Database to find documents to update
* @param viewName
* String view name to find documents to update
* @since org.openntf.domino 1.0.0
*/
public void setTargetDatabase(final Database db, final String viewName) {
setTargetServer(db.getServer());
setTargetFilepath(db.getFilePath());
setTargetLookupView(viewName);
}
/**
* Gets the target server.
*
* @return String the target server
* @since org.openntf.domino 1.0.0
*/
public String getTargetServer() {
return targetServer_;
}
/**
* Sets the target server.
*
* @param targetServer
* String the new target server
* @since org.openntf.domino 1.0.0
*/
public void setTargetServer(final String targetServer) {
targetServer_ = targetServer;
}
/**
* Gets the target filepath.
*
* @return String the target filepath
* @since org.openntf.domino 1.0.0
*/
public String getTargetFilepath() {
return targetFilepath_;
}
/**
* Sets the target filepath.
*
* @param targetFilepath
* String the new target filepath
* @since org.openntf.domino 1.0.0
*/
public void setTargetFilepath(final String targetFilepath) {
targetFilepath_ = targetFilepath;
}
/**
* Gets the target lookup view.
*
* @return String the target lookup view name
* @since org.openntf.domino 1.0.0
*/
public String getTargetLookupView() {
return targetLookupView_;
}
/**
* Sets the target lookup view.
*
* @param targetLookupView
* String the new target lookup view name
* @since org.openntf.domino 1.0.0
*/
public void setTargetLookupView(final String targetLookupView) {
targetLookupView_ = targetLookupView;
}
/**
* Gets the source key using an Item name or Formula.
*
* @return String the source key formula
* @since org.openntf.domino 1.0.0
*/
public Formula getSourceKeyFormula() {
if (sourceKeyFormula_ == null) {
sourceKeyFormula_ = new Formula();
}
return sourceKeyFormula_;
}
/**
* Sets the source key using an Item name of Formula.
*
* @param sourceKeyFormula
* String the new source key formula
* @since org.openntf.domino 1.0.0
*/
public void setSourceKeyFormula(final String sourceKeyFormula) {
if (sourceKeyFormula_ == null) {
sourceKeyFormula_ = new Formula();
}
sourceKeyFormula_.setExpression(sourceKeyFormula);
}
/**
* Gets the strategy.
*
* @return Strategy to apply
* @since org.openntf.domino 1.0.0
*/
public Strategy getStrategy() {
return strategy_;
}
/**
* Gets the transaction rule.
*
* @return TransactionRule to apply
* @since org.openntf.domino 1.0.0
*/
public TransactionRule getTransactionRule() {
if (transactionRule_ == null) {
transactionRule_ = TransactionRule.NO_TRANSACTION;
}
return transactionRule_;
}
/**
* Sets the strategy.
*
* @param strategy
* Strategy to apply
* @since org.openntf.domino 1.0.0
*/
public void setStrategy(final Strategy strategy) {
strategy_ = strategy;
}
/**
* Sets the transaction rule.
*
* @param rule
* TransactionRule to apply
* @since org.openntf.domino 1.0.0
*/
public void setTransactionRule(final TransactionRule rule) {
transactionRule_ = rule;
}
/**
* Gets the sync map of Item names or Formulas to apply to the source document and Item names on the target documents into which to
* store the result
*
* @return Map<Formula, String> the sync map
* @since org.openntf.domino 1.0.0
*/
public Map<Formula, String> getSyncMap() {
return syncMap_;
}
/**
* Sets the sync mapof Item names or Formulas to apply to the source document and Item names on the target documents into which to store
* the result
*
* @param syncMap
* Map<Formula, String> the sync map
* @since org.openntf.domino 1.0.0
*/
public void setSyncMap(final Map<java.lang.Object, String> syncMap) {
Map<Formula, String> formulaMap = new HashMap<Formula, String>();
for (Map.Entry<Object, String> entry : syncMap.entrySet()) {
if (entry.getKey() instanceof Formula) {
formulaMap.put((Formula) entry.getKey(), entry.getValue());
}
Formula Fkey = new Formula();
Fkey.setExpression(String.valueOf(entry.getKey()));
formulaMap.put(Fkey, entry.getValue());
}
syncMap_ = formulaMap;
}
}