/**
*
*/
package org.topicquests.topicmap.json.merge;
import java.util.*;
import org.topicquests.common.ResultPojo;
import org.topicquests.common.api.ICoreIcons;
import org.topicquests.common.api.IResult;
import org.topicquests.common.api.ITopicQuestsOntology;
//import org.topicquests.model.api.IDataProvider;
//import org.topicquests.model.api.IEnvironment;
import org.topicquests.model.api.node.INode;
import org.topicquests.model.api.node.INodeModel;
import org.topicquests.model.api.ITicket;
import org.topicquests.model.api.node.ITuple;
import org.topicquests.model.api.query.ITupleQuery;
import org.topicquests.topicmap.json.model.JSONTopicmapEnvironment;
import org.topicquests.topicmap.json.model.api.IJSONTopicDataProvider;
/**
* @author park
* <p>SetUnion functions for all VirtualNode creation
*/
public class BaseVirtualizer {
protected JSONTopicmapEnvironment environment;
protected IJSONTopicDataProvider database;
protected INodeModel nodeModel;
protected ITupleQuery tupleQuery;
public void init(JSONTopicmapEnvironment env) {
environment = (JSONTopicmapEnvironment)env;
database = (IJSONTopicDataProvider)environment.getDataProvider();
nodeModel = database.getNodeModel();
tupleQuery = database.getTupleQuery();
}
/**
* <p>Perform a <em>Set Union</em> on various key/value pairs</p>
* <p>This is ONLY appropriate for creation of a virtual proxy</p>
* @param virtualNode
* @param mergedNode
* @param isTuple
* @return
*/
protected boolean setUnionProperties(INode virtualNode, INode mergedNode) {
//if any surgery is performed, result = true
boolean result = false;
//Copy over all labels and details for both nodes
// installLabelsAndDetails(virtualNode,mergedNode);
//grab unrestricted tuples first
environment.logDebug("BaseVirtualizer.setUnionProperties- "+virtualNode.getLocator()+" "+mergedNode.getLocator());
//other properties -- doing surgery on the map
//note: this will pick up all the labels and details in all languages
Map<String,Object>sourceMap = mergedNode.getProperties();
Map<String,Object>virtMap = virtualNode.getProperties();
Iterator<String>keys = sourceMap.keySet().iterator();
String key;
Object os;
Object ov;
List<String>sxx;
List<String>vxx;
while (keys.hasNext()) {
key = keys.next();
environment.logDebug("BaseVirtualizer.setUnionProperties-1 "+key);
if (okToUse(key)) {
os = sourceMap.get(key);
ov = virtMap.get(key);
environment.logDebug("BaseVirtualizer.setUnionProperties-2 "+key+" "+ov+" "+os);
if (os instanceof String ||
os instanceof Date ||
os instanceof Long ||
os instanceof Double ||
os instanceof Integer ||
os instanceof Float ||
os instanceof Boolean) {
///////////////////////////////////////
//This may be where we are creating dates as strings
//and messing things up
///////////////////////////////////////
if (ov == null && !(key.equals(ITopicQuestsOntology.CREATED_DATE_PROPERTY) ||
key.equals(ITopicQuestsOntology.LAST_EDIT_DATE_PROPERTY))) {
virtMap.put(key, os);
result = true;
} else if (ov instanceof String && os instanceof String) {
//ov and os are strings: same key; make a list
if (!ov.equals(os)) {
vxx = new ArrayList<String>();
vxx.add((String)ov);
vxx.add((String)os);
virtMap.put(key, vxx);
environment.logDebug("BaseVirtualizer.setUnionProperties-3 "+key+" "+vxx);
}
} else if (ov instanceof List) {
vxx = (List<String>)ov;
if (!vxx.isEmpty() && !vxx.contains((String)os)) {
vxx.add((String)os);
virtMap.put(key, vxx);
environment.logDebug("BaseVirtualizer.setUnionProperties-4 "+key+" "+vxx+" "+virtMap);
}
} else {
environment.logDebug("WIERD "+key+" "+ov+" | "+os);
}
} else { //os must be a list
sxx = (List<String>)os;
environment.logDebug("BaseVirtualizer.setUnionProperties-5 "+key+" "+sxx);
if (ov == null)
vxx = new ArrayList<String>();
else //TODO TEST FOR LIST OR STRING XXXXX
vxx = (List<String>)ov;
int len = sxx.size();
for (int i=0;i<len;i++) {
if (!vxx.contains(sxx.get(i))) {
vxx.add(sxx.get(i));
result = true;
}
}
if (!vxx.isEmpty()) {
virtMap.put(key, vxx);
environment.logDebug("BaseVirtualizer.setUnionProperties-5a "+key+" "+vxx);
}
}
}
}
//update last edit
if (result)
virtMap.put(ITopicQuestsOntology.LAST_EDIT_DATE_PROPERTY, new Date());
return result;
}
/**
* Filter out certain keys
* @param key
* @return
*/
boolean okToUse(String key) {
if (key.equals(ITopicQuestsOntology.LOCATOR_PROPERTY) ||
key.equals(ITopicQuestsOntology.CREATED_DATE_PROPERTY) ||
key.equals(ITopicQuestsOntology.LAST_EDIT_DATE_PROPERTY) ||
key.equals(ITopicQuestsOntology.CREATOR_ID_PROPERTY) ||
key.equals(ITopicQuestsOntology.IS_PRIVATE_PROPERTY))
return false;
return true;
}
/**
* Adding to a singleValued field
* @param key
* @return false if not ok to add
*/
boolean okToAdd(String key) {
if (key.equals(ITopicQuestsOntology.LOCATOR_PROPERTY) ||
key.equals(ITopicQuestsOntology.CREATED_DATE_PROPERTY) ||
key.equals(ITopicQuestsOntology.LAST_EDIT_DATE_PROPERTY) ||
key.equals(ITopicQuestsOntology.CREATOR_ID_PROPERTY) ||
key.equals(ITopicQuestsOntology.PSI_PROPERTY_TYPE) ||
key.equals(ITopicQuestsOntology.RESOURCE_URL_PROPERTY) ||
key.equals(ITopicQuestsOntology.INSTANCE_OF_PROPERTY_TYPE) ||
key.equals(ITopicQuestsOntology.IS_PRIVATE_PROPERTY))
return false;
return true;
}
/**
* Wire up a MergeAssertion
* @param virtualProxy
* @param targetProxy
* @param mergeData key = reason, value = vote
* @param mergeConfidence not used at the moment
* @param userLocator
* @return tupleLocator is included
*/
IResult wireMerge(INode virtualProxy, INode targetProxy, Map<String, Double> mergeData,
double mergeConfidence, String userLocator) {
//force relation engine to re-fetch the nodes for accurate surgery
//changed my mind
//TODO rethink this algorithm; it's a lot of node fetches
IResult result = relateNodes(virtualProxy, targetProxy,
userLocator, ICoreIcons.RELATION_ICON_SM, ICoreIcons.RELATION_ICON,
false, targetProxy.getIsPrivate(), mergeData);
// result contains the tuple's locator
// use that to fetch it and wire up scopes from mergeData
return result;
}
/**
* Forge a merge relation--not using the version in INodeModel
* @param virtualNode
* @param targetNode
* @param userId
* @param smallImagePath
* @param largeImagePath
* @param isTransclude
* @param isPrivate
* @param mergeData
* @return locator of the created tuple
*/
private IResult relateNodes(INode virtualNode, INode targetNode,
String userId, String smallImagePath,
String largeImagePath, boolean isTransclude, boolean isPrivate, Map<String, Double> mergeData) {
String relationTypeLocator = ITopicQuestsOntology.MERGE_ASSERTION_TYPE;
database.removeFromCache(virtualNode.getLocator());
database.removeFromCache(targetNode.getLocator());
IResult result = new ResultPojo();
String signature = virtualNode.getLocator()+ITopicQuestsOntology.MERGE_ASSERTION_TYPE+targetNode.getLocator();
//NOTE that we make the tuple an instance of the relation type, not of TUPLE_TYPE
ITuple t = (ITuple)nodeModel.newInstanceNode(signature, relationTypeLocator,
virtualNode.getLocator()+" "+relationTypeLocator+" "+targetNode.getLocator(), "en", userId, smallImagePath, largeImagePath, isPrivate);
t.setIsTransclude(isTransclude);
//NOTE: tuple object is always merged node
t.setObject(targetNode.getLocator());
t.setObjectType(ITopicQuestsOntology.NODE_TYPE);
//NOTE: tuple subject is always virtualProxy
t.setSubjectLocator(virtualNode.getLocator());
t.setSubjectType(ITopicQuestsOntology.NODE_TYPE);
Iterator<String>itx = mergeData.keySet().iterator();
String reason;
while (itx.hasNext()) {
reason = itx.next();
t.addMergeReason(reason+" "+mergeData.get(reason));
}
IResult x = database.putNode(t, true);
environment.logDebug("MergeBean.relateNodes "+virtualNode.getLocator()+" "+targetNode.getLocator()+" "+t.getLocator());
if (x.hasError())
result.addErrorString(x.getErrorString());
String tLoc = t.getLocator();
//save the tuple's locator in the output
result.setResultObject(tLoc);
addPropertyValue(virtualNode,ITopicQuestsOntology.MERGE_TUPLE_PROPERTY,tLoc);
//add the value to the node after it's been surgically added
virtualNode.addMergeTupleLocator(tLoc);
targetNode.addMergeTupleLocator(tLoc);
changePropertyValue(targetNode,ITopicQuestsOntology.MERGE_TUPLE_PROPERTY,tLoc);
environment.logDebug("MergeBean.relateNodes+ "+result.getErrorString());
return result;
}
/**
* Surgically change a property value (NOT a list value)
* @param node
* @param key
* @param newValue
* @return
*/
void changePropertyValue(INode node, String key, String newValue) {
environment.logDebug("MergeBean.changePropertyValue- "+node.getLocator()+" "+key+" "+newValue);
Map<String,Object>p =node.getProperties();
p.put(key, newValue);
database.removeFromCache(node.getLocator());
}
void addPropertyValue(INode node, String key, String newValue) {
environment.logDebug("MergeBean.addPropertyValue- "+node.getLocator()+" "+key+" "+newValue);
String sourceNodeLocator = node.getLocator();
Map<String,Object> propMap = node.getProperties();
Object ox = propMap.get(key);
if (ox instanceof String)
propMap.put(key, newValue);
else {
List<String> l = (List<String>)ox;
l.add(newValue);
}
database.removeFromCache(node.getLocator());
}
/////////////////////////////////////////////////////////////////
// Rewiring the graph
// CASE: Merged nodes
// A Node was merged with another node.
// A VirtualNode was created
// For every place in the graph where either node is referenced
// surgically replace that node locator with the virtual node locator
// CASE: Merged tuples
// A Tuple has merged with another tuple
// NOTE: these are NOT Merge-related tuples, only knowledge graph tuples
// For every node which references a merged tuple in its tuple field
// surgically replace that tuple locator with the new tuple locator
// this entails overwriting a tuple *in place* in a list of tuples
// and doing an update on that list in the database
/////////////////////////////////////////////////////////////////
// Emergent Issue
// If a new node is saved and it is immediately related to another node
// it will be in a merge process while the relation is occuring.
// THIS means that there MIGHT be NO TUPLE available to be captured
// during the merge. Thus, the VirtualNode MIGHT end up with no tuple
//////////////////////////////////////////////////////////////////
/**
* Substitute <code>virtualProxyLocator</code> for all hits of <code>mergedProxyLocator</code>
* @param mergedProxyLocator
* @param virtualProxyLocator
* @return
*/
IResult reWireNodeGraph(String mergedProxyLocator, String virtualProxyLocator, String mergeTupleLocator, ITicket credentials) {
//this really must deal with tuples first
//TODO sort out what other propertyTypes entail symbolic links, then
//chase those
environment.logDebug("MergeBean.reWireNodeGraph- "+mergedProxyLocator+" "+virtualProxyLocator+" "+mergeTupleLocator);
IResult result = new ResultPojo();
//Find all tuples where mergedProxyLocator isA subject and fix them
IResult xx = tupleQuery.listTuplesBySubject(mergedProxyLocator, 0,-1,credentials);
if (xx.hasError())
result.addErrorString(xx.getErrorString());
List<INode>n = (List<INode>)xx.getResultObject();
ITuple t;
Iterator<INode>itr;
IResult surgR;
if (n != null && !n.isEmpty()) {
//time for surgery
itr = n.iterator();
while (itr.hasNext()) {
t = (ITuple)itr.next();
if (!t.getLocator().equals(mergeTupleLocator)) {
environment.logDebug("MergeBean.reWireGraph-1 "+t.getLocator());
surgR = performTupleSurgery(t,virtualProxyLocator,true);
if (surgR.hasError())
result.addErrorString(surgR.getErrorString());
//notice the distinct possibility that no surgery got performed
//and the topic map will have errors
}
}
}
//Find all tuples where mergedProxyLocator isA object and fix them
xx = tupleQuery.listTuplesByObjectLocator(mergedProxyLocator, 0,-1, credentials);
if (xx.hasError())
result.addErrorString(xx.getErrorString());
n = (List<INode>)xx.getResultObject();
if (n != null && !n.isEmpty()) {
//time for surgery
itr = n.iterator();
while (itr.hasNext()) {
t = (ITuple)itr.next();
if (!t.getLocator().equals(mergeTupleLocator)) {
environment.logDebug("MergeBean.reWireGraph-2 "+t.getLocator());
surgR = performTupleSurgery(t,virtualProxyLocator,false);
if (surgR.hasError())
result.addErrorString(surgR.getErrorString());
//notice the distinct possibility that no surgery got performed
//and the topic map will have errors
}
}
}
return result;
}
/**
* <p>This is supposed to perform surgery on {@link ITuple} objects only.</p>
* @param t
* @param newLocator
* @param isSubject
* @return
*/
IResult performTupleSurgery(ITuple t, String newLocator, boolean isSubject) {
String key = ITopicQuestsOntology.TUPLE_SUBJECT_PROPERTY;
if (!isSubject)
key = ITopicQuestsOntology.TUPLE_OBJECT_PROPERTY;
//We are performing surgery on a tuple which might have already had
//surgery earlier, which means the version number will be out of date.
//Two options: remove the version number, or refetch the tuple before
// doing this surgery
///////////////////////////////
//TODO: must pay attention to changes in date fields
///////////////////////////////
environment.logDebug("MergeBean.performTupleSergery "+t.getLocator()+" "+newLocator);
IResult result = nodeModel.changePropertyValue(t, key, newLocator);
return result;
}
}