package com.bagri.server.hazelcast.impl;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSchema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bagri.core.api.BagriException;
import com.bagri.core.model.Document;
import com.bagri.core.model.Transaction;
import com.bagri.core.server.api.DocumentTrigger;
import com.bagri.core.server.api.TriggerManagement;
import com.bagri.core.system.JavaTrigger;
import com.bagri.core.system.Library;
import com.bagri.core.system.Module;
import com.bagri.core.system.TriggerAction;
import com.bagri.core.system.TriggerDefinition;
import com.bagri.core.system.XQueryTrigger;
import com.bagri.core.system.TriggerAction.Order;
import com.bagri.core.system.TriggerAction.Scope;
import com.bagri.core.xquery.api.XQCompiler;
import com.bagri.server.hazelcast.task.trigger.TriggerRunner;
import com.bagri.support.stats.StatisticsEvent;
import com.bagri.support.util.FileUtils;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IExecutorService;
public class TriggerManagementImpl implements TriggerManagement {
private static final transient Logger logger = LoggerFactory.getLogger(TriggerManagementImpl.class);
private HazelcastInstance hzInstance;
//private IMap<Integer, TriggerDefinition> trgDict;
private Map<String, List<TriggerContainer>> triggers = new HashMap<>();
private IExecutorService execService;
private boolean enableStats = true;
private BlockingQueue<StatisticsEvent> queue;
private SchemaRepositoryImpl repo = null; //
private XQCompiler xqComp;
public void setHzInstance(HazelcastInstance hzInstance) {
this.hzInstance = hzInstance;
}
public void setRepository(SchemaRepositoryImpl repo) {
this.repo = repo;
}
public void setXQCompiler(XQCompiler xqComp) {
this.xqComp = xqComp;
}
public void setExecService(IExecutorService execService) {
this.execService = execService;
}
public void setStatsQueue(BlockingQueue<StatisticsEvent> queue) {
this.queue = queue;
}
public void setStatsEnabled(boolean enable) {
this.enableStats = enable;
}
private String getTriggerKey(String root, Order order, Scope scope) {
return root + ":" + order.name() + ":" + scope.name();
}
void applyTrigger(final Document xDoc, final Order order, final Scope scope) throws BagriException {
//
String key = getTriggerKey(xDoc.getTypeRoot(), order, scope);
List<TriggerContainer> impls = triggers.get(key);
if (impls != null) {
for (TriggerContainer impl: impls) {
logger.trace("applyTrigger; about to fire trigger {}, on document: {}", impl, key);
final DocumentTrigger trigger = impl.getImplementation();
if (impl.isSynchronous()) {
runTrigger(order, scope, xDoc, trigger);
} else {
String clientId = repo.getClientId();
execService.submitToMember(new TriggerRunner(order, scope, impl.getIndex(), xDoc, clientId),
hzInstance.getCluster().getLocalMember());
}
}
}
}
void applyTrigger(final Transaction xTx, final Order order, final Scope scope) throws BagriException {
// TODO: implement me!
// before/after begin/commit/rollback transaction
}
public void runTrigger(Order order, Scope scope, Document xDoc, int index, String clientId) throws BagriException {
String key = getTriggerKey(xDoc.getTypeRoot(), order, scope);
List<TriggerContainer> impls = triggers.get(key);
if (impls != null) {
TriggerContainer impl = impls.get(index);
repo.getXQProcessor(clientId);
runTrigger(order, scope, xDoc, impl.getImplementation());
}
}
private void runTrigger(Order order, Scope scope, Document xDoc, DocumentTrigger trigger) throws BagriException {
String trName = order + " " + scope;
try {
if (order == Order.before) {
if (scope == Scope.insert) {
trigger.beforeInsert(xDoc, repo);
} else if (scope == Scope.delete) {
trigger.beforeDelete(xDoc, repo);
} else {
trigger.beforeUpdate(xDoc, repo);
}
} else {
if (scope == Scope.insert) {
trigger.afterInsert(xDoc, repo);
} else if (scope == Scope.delete) {
trigger.afterDelete(xDoc, repo);
} else {
trigger.afterUpdate(xDoc, repo);
}
}
updateStats(trName, true, 1);
} catch (Throwable ex) {
logger.error("applyTrigger.error; exception on trigger [" + trName + "] :", ex);
updateStats(trName, false, 1);
throw ex;
}
}
private void updateStats(String name, boolean success, int count) {
if (enableStats) {
if (!queue.offer(new StatisticsEvent(name, success, new Object[] {count}))) {
logger.warn("updateStats; queue is full!!");
}
}
}
@Override
public boolean createTrigger(TriggerDefinition trigger) {
logger.trace("createTrigger.enter; trigger: {}", trigger);
boolean result = false;
if (trigger.isEnabled()) {
DocumentTrigger impl;
if (trigger instanceof JavaTrigger) {
impl = createJavaTrigger((JavaTrigger) trigger);
} else {
impl = createXQueryTrigger((XQueryTrigger) trigger);
}
if (impl != null) {
for (TriggerAction action: trigger.getActions()) {
String key = getTriggerKey(trigger.getDocType(), action.getOrder(), action.getScope());
List<TriggerContainer> impls = triggers.get(key);
if (impls == null) {
impls = new LinkedList<>();
triggers.put(key, impls);
}
int index = trigger.getIndex();
if (index > impls.size()) {
logger.info("createTrigger; wrong trigger index specified: {}, when size is: {}",
index, impls.size());
index = impls.size();
}
TriggerContainer cont = new TriggerContainer(index, trigger.isSynchronous(), impl);
impls.add(index, cont);
}
result = true;
logger.trace("createTrigger; registered so far: {}", triggers);
}
}
logger.trace("createTrigger.exit; returning: {}", result);
return result;
}
private DocumentTrigger createJavaTrigger(JavaTrigger trigger) {
Library library = getLibrary(trigger.getLibrary());
if (library == null) {
logger.info("createJavaTrigger; not library found for name: {}, trigger registration failed",
trigger.getLibrary());
return null;
}
if (!library.isEnabled()) {
logger.info("createJavaTrigger; library {} disabled, trigger registration failed",
trigger.getLibrary());
return null;
}
Class tc = null;
try {
tc = Class.forName(trigger.getClassName());
} catch (ClassNotFoundException ex) {
// load library dynamically..
logger.debug("createJavaTrigger; ClassNotFound: {}, about to load library..", trigger.getClassName());
try {
addURL(FileUtils.path2url(library.getFileName()));
tc = Class.forName(trigger.getClassName());
introspectLibrary(library.getFileName());
} catch (ClassNotFoundException | IOException ex2) {
logger.error("createJavaTrigger.error; ", ex2);
}
}
if (tc != null) {
try {
DocumentTrigger triggerImpl = (DocumentTrigger) tc.newInstance();
return triggerImpl;
} catch (InstantiationException | IllegalAccessException ex) {
logger.error("createJavaTrigger.error; {}", ex);
}
}
return null;
}
private DocumentTrigger createXQueryTrigger(XQueryTrigger trigger) {
Module module = getModule(trigger.getModule());
if (module == null) {
logger.info("createXQueryTrigger; not module found for name: {}, trigger registration failed",
trigger.getModule());
return null;
}
if (!module.isEnabled()) {
logger.info("createXQueryTrigger; module {} disabled, trigger registration failed",
trigger.getModule());
return null;
}
if (!xqComp.getModuleState(module)) {
logger.info("createXQueryTrigger; module {} is invalid, trigger registration failed",
trigger.getModule());
return null;
}
try {
String query = xqComp.compileTrigger(module, trigger);
DocumentTrigger impl = new XQueryTriggerImpl(query);
return impl;
} catch (BagriException ex) {
logger.info("createXQueryTrigger; trigger function {} is invalid, trigger registration failed",
trigger.getFunction());
}
return null;
}
private Library getLibrary(String library) {
Collection<Library> libraries = repo.getLibraries();
for (Library xLib: libraries) {
if (library.equals(xLib.getName())) {
return xLib;
}
}
logger.trace("getLibrary; libraries: {}", libraries);
return null;
}
private Module getModule(String module) {
Collection<Module> modules = repo.getModules();
for (Module xModule: modules) {
if (module.equals(xModule.getName())) {
return xModule;
}
}
logger.trace("getModule; modules: {}", modules);
return null;
}
@Override
public boolean deleteTrigger(TriggerDefinition trigger) {
int cnt = 0;
for (TriggerAction action: trigger.getActions()) {
String key = getTriggerKey(trigger.getDocType(), action.getOrder(), action.getScope());
List<TriggerContainer> impls = triggers.get(key);
if (impls != null) {
impls.remove(trigger.getIndex());
cnt++;
}
}
logger.trace("deleteTrigger.exit; {} triggers deleted, for type: {}", cnt, trigger.getDocType());
return cnt > 0;
}
private void addURL(URL u) throws IOException {
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
try {
Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] {URL.class});
method.setAccessible(true);
method.invoke(sysloader, new Object[] {u});
} catch (Throwable ex) {
throw new IOException("Error, could not add URL to system classloader", ex);
}
}
private void introspectLibrary(String libName) throws IOException {
Map<String, Class> classes = new HashMap<>();
Map<String, String> packages = new HashMap<>();
try (JarFile jar = new JarFile(libName)) {
for (Enumeration<JarEntry> entries = jar.entries(); entries.hasMoreElements();) {
JarEntry entry = entries.nextElement();
String file = entry.getName();
//logger.trace("introspectLibrary; entry: {}", entry);
if (file.endsWith(".class")) {
String classname = file.replace('/', '.').substring(0, file.length() - 6);
try {
Class<?> cls = Class.forName(classname);
XmlRootElement aRoot = cls.getAnnotation(XmlRootElement.class);
if (aRoot != null) {
classes.put(cls.getName(), cls);
logger.trace("introspectLibrary; added class: {} for path: {}:{}",
cls.getName(), aRoot.namespace(), aRoot.name());
} else {
XmlSchema aSchema = cls.getAnnotation(XmlSchema.class);
if (aSchema != null) {
packages.put(cls.getPackage().getName(), aSchema.namespace());
logger.trace("introspectLibrary; added namespace: {} for package: {}",
aSchema.namespace(), cls.getPackage().getName());
}
}
} catch (Throwable ex) {
logger.error("introspectLibrary.error", ex);
}
}
}
}
}
}