package org.orienteer.core.hook;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.orientechnologies.orient.core.hook.ODocumentHookAbstract;
import com.orientechnologies.orient.core.hook.ORecordHook;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.impl.ODocument;
/**
* OrientDB {@link ORecordHook} which allows to bind runtime callback to a document.
* Important: callbacks can preserve serialization of a document (for example newly created doc)
*/
public class CallbackHook implements ORecordHook {
private static final String CALLBACKS_FIELD = "__callbacks__";
private static final Set<TYPE> PRESERVE_IN_THREAD = new HashSet<TYPE>(Arrays.asList(TYPE.BEFORE_CREATE, TYPE.BEFORE_READ, TYPE.BEFORE_UPDATE, TYPE.BEFORE_DELETE));
private static final ThreadLocal<CallbacksHolder> PRESERVED = new ThreadLocal<CallbackHook.CallbacksHolder>();
/**
* Callback to be executed on event
*/
public static interface ICallback extends Serializable {
/**
* Called when required event occur
* @param iType type of an event
* @param doc {@link ODocument}
* @return true if document was changed, false - if not
*/
public boolean call(TYPE iType, ODocument doc);
}
private static class CallbacksHolder implements Serializable {
private final Map<TYPE, List<ICallback>> callbacksMap = new HashMap<ORecordHook.TYPE, List<ICallback>>();
public void registerCallback(TYPE type, ICallback callback) {
List<ICallback> list = callbacksMap.get(type);
if(list==null) {
list = new LinkedList<CallbackHook.ICallback>();
callbacksMap.put(type, list);
}
list.add(callback);
}
public boolean call(TYPE type, ODocument doc) {
List<ICallback> callbacks = callbacksMap.remove(type);
boolean ret = false;
if(callbacks!=null) {
for (ICallback iCallback : callbacks) {
ret= iCallback.call(type, doc) || ret;
}
}
return ret;
}
public boolean contains(TYPE type) {
return callbacksMap.containsKey(type);
}
private void optimize() {
if(callbacksMap.isEmpty()) return;
Iterator<Map.Entry<TYPE, List<ICallback>>> it = callbacksMap.entrySet().iterator();
while(it.hasNext()){
Map.Entry<TYPE, List<ICallback>> entry = it.next();
List<ICallback> list = entry.getValue();
if(list==null || list.isEmpty()) it.remove();
}
}
public boolean isEmpty() {
optimize();
return callbacksMap.isEmpty();
}
}
public static void registerCallback(ODocument doc, TYPE type, ICallback callback) {
CallbacksHolder ret = (CallbacksHolder) doc.field(CALLBACKS_FIELD);
if(ret==null) {
ret = new CallbacksHolder();
//Field type should be CUSTOM: to allow callback be serialized and still preserved in a doc
doc.field(CALLBACKS_FIELD, ret, OType.CUSTOM);
}
ret.registerCallback(type, callback);
}
@Override
public RESULT onTrigger(TYPE iType, ORecord iRecord) {
if(!(iRecord instanceof ODocument)) return RESULT.RECORD_NOT_CHANGED;
ODocument doc = (ODocument) iRecord;
CallbacksHolder callbacks=null;
if(doc.containsField(CALLBACKS_FIELD)) callbacks = (CallbacksHolder) doc.field(CALLBACKS_FIELD);
else callbacks = PRESERVED.get();
if(callbacks==null) return RESULT.RECORD_NOT_CHANGED;
boolean docWasChanged = callbacks.call(iType, doc);
PRESERVED.remove();
if(PRESERVE_IN_THREAD.contains(iType)) {
doc.removeField(CALLBACKS_FIELD);
PRESERVED.set(callbacks);
}
if(callbacks.isEmpty()) doc.removeField(CALLBACKS_FIELD);
return docWasChanged?RESULT.RECORD_CHANGED:RESULT.RECORD_NOT_CHANGED;
}
@Override
public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() {
return DISTRIBUTED_EXECUTION_MODE.SOURCE_NODE;
}
@Override
public void onUnregister() {
}
}