package org.openntf.domino.xsp.xots;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import lotus.domino.Session;
import org.eclipse.core.runtime.Platform;
import org.openntf.domino.session.NativeSessionFactory;
import org.openntf.domino.thread.DominoExecutor;
import org.openntf.domino.thread.IWrappedCallable;
import org.openntf.domino.thread.IWrappedRunnable;
import org.openntf.domino.utils.DominoUtils;
import org.openntf.domino.utils.Factory;
import org.openntf.domino.utils.Factory.SessionType;
import org.openntf.domino.xots.AbstractXotsCallable;
import org.openntf.domino.xots.AbstractXotsRunnable;
import org.openntf.domino.xots.Tasklet;
import org.openntf.domino.xots.Tasklet.Context;
import org.openntf.domino.xots.XotsContext;
import org.openntf.domino.xsp.ODAPlatform;
import org.openntf.domino.xsp.session.InvalidSessionFactory;
import org.openntf.domino.xsp.session.XPageNamedSessionFactory;
import org.openntf.domino.xsp.session.XPageSignerSessionFactory;
import org.osgi.framework.Bundle;
import com.ibm.commons.util.ThreadLockManager;
import com.ibm.designer.runtime.domino.adapter.ComponentModule;
import com.ibm.domino.xsp.module.nsf.ModuleClassLoader;
import com.ibm.domino.xsp.module.nsf.NSFComponentModule;
import com.ibm.domino.xsp.module.nsf.NotesContext;
public class XotsDominoExecutor extends DominoExecutor {
private static final Logger log_ = Logger.getLogger(XotsDominoExecutor.class.getName());
/**
*
* @author Roland Praml, FOCONIS AG
*
*/
public static class XotsWrappedCallable<V> extends XotsWrappedTask implements IWrappedCallable<V> {
private NSFComponentModule module_;
public XotsWrappedCallable(final NSFComponentModule module, final Callable<V> wrappedObject) {
setWrappedTask(wrappedObject);
module_ = module;
}
@SuppressWarnings("unchecked")
@Override
public V call() throws Exception {
return (V) callOrRun(module_);
}
}
/**
*
* @author Roland Praml, FOCONIS AG
*
*/
public static class XotsWrappedRunnable extends XotsWrappedTask implements IWrappedRunnable {
private NSFComponentModule module_;
public XotsWrappedRunnable(final NSFComponentModule module, final Runnable wrappedObject) {
setWrappedTask(wrappedObject);
module_ = module;
}
@Override
public void run() {
try {
callOrRun(module_);
} catch (Exception e) {
log_.log(Level.SEVERE, "Could not execute " + module_.getModuleName() + "/" + getWrappedTask().getClass(), e);
}
}
}
/**
* This class is used for periodic tasks.
*
* @author Roland Praml, FOCONIS AG
*
*/
protected static class XotsBundleTasklet extends XotsWrappedTask implements IWrappedCallable<Object> {
private String description;
public XotsBundleTasklet(final String bundleName, final String className, final Object[] args) {
super();
final Bundle bundle = Platform.getBundle(bundleName);
Class<?> clazz = null;
if (bundle == null)
throw new IllegalArgumentException("Could not find bundle " + bundleName);
try {
clazz = bundle.loadClass(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Could not load class " + className + " in bundle " + bundleName, e);
}
description = "bundle:" + bundleName + ":" + className;
Constructor<?> cTor = findConstructor(clazz, args);
try {
setWrappedTask(cTor.newInstance(args));
} catch (Exception e) {
DominoUtils.handleException(e);
} // This WrappedCallable has no tasklet if it does NOT run
}
@Override
public Object call() throws Exception {
return callOrRun(null);
}
@Override
public String getDescription() {
return description;
}
}
protected static class XotsModuleTasklet extends XotsWrappedTask implements IWrappedCallable<Object> {
private String moduleName;
private String className;
private Object[] args;
private ArrayList<Observer> observers;
public XotsModuleTasklet(final String moduleName, final String className, final Object... args) {
super();
this.moduleName = moduleName;
this.className = className;
this.args = args;
if (NotesContext.getCurrentUnchecked() == null) {
// perform a check if there is NO open context.
// if there is an open context, we cannot switch the module.
// maybe we can do this in a separate thread
NSFComponentModule module = loadModule();
NotesContext ctx = new NotesContext(module);
NotesContext.initThread(ctx);
try {
Factory.initThread(ODAPlatform.getAppThreadConfig(module.getNotesApplication()));
try {
ClassLoader mcl = module.getModuleClassLoader();
ClassLoader oldCl = switchClassLoader(mcl);
Factory.setClassLoader(mcl);
try {
Class<?> clazz = mcl.loadClass(className);
findConstructor(clazz, args); // try if we can find the constructor
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Could not load class " + className + " in module " + moduleName, e);
} finally {
switchClassLoader(oldCl);
}
} finally {
Factory.termThread();
}
} finally {
NotesContext.termThread();
}
}
}
/**
* loads the module
*
* @return
*/
protected NSFComponentModule loadModule() {
try {
NSFComponentModule module = ModuleLoader.loadModule(moduleName, true);
if (module == null)
throw new IllegalArgumentException("Could not find bundle " + moduleName);
return module;
} catch (ServletException e) {
throw new RuntimeException(e);
}
}
@Override
public void addObserver(final Observer o) {
if (observers == null) {
observers = new ArrayList<Observer>();
}
observers.add(o);
}
@Override
public Object call() throws Exception {
NSFComponentModule module = loadModule();
NotesContext ctx = new NotesContext(module);
NotesContext.initThread(ctx);
try {
// CHECKME which username should we use? Server
ctx.initRequest(new FakeHttpRequest(Factory.getLocalServerName()));
// ThreadLock readLock = null;
//
// if (ODAPlatform.isAppFlagSet("LOCKXOTS", codeModule.getNotesApplication())) {
// readLock = getLockManager(codeModule).getReadLock();
// readLock.acquire();
// }
// try {
Factory.initThread(ODAPlatform.getAppThreadConfig(module.getNotesApplication()));
try {
ClassLoader mcl = module.getModuleClassLoader();
ClassLoader oldCl = switchClassLoader(mcl);
Factory.setClassLoader(mcl);
Factory.setSessionFactory(new NativeSessionFactory(moduleName), SessionType.CURRENT);
DominoUtils.setBubbleExceptions(true);
try {
// Construct & set up
Class<?> clazz = mcl.loadClass(className);
Constructor<?> cTor = findConstructor(clazz, args);
setWrappedTask(cTor.newInstance(args));
Object wrappedTask = getWrappedTask();
if (wrappedTask instanceof Observable && observers != null) {
for (Observer o : observers) {
((Observable) wrappedTask).addObserver(o);
}
}
return invokeTasklet(ctx, module);
} finally {
switchClassLoader(oldCl);
}
} finally {
setWrappedTask(null);
Factory.termThread();
}
// } finally {
// if (readLock != null)
// readLock.release();
// }
} finally {
NotesContext.termThread();
}
}
@Override
public String getDescription() {
return moduleName + ":" + className;
}
}
public XotsDominoExecutor(final int corePoolSize) {
super(corePoolSize, "Xots");
}
/**
* We lock the module, so that it cannot get refreshed. (hope this is a good idea)
*
*/
private static Field lockManager_field = AccessController.doPrivileged(new PrivilegedAction<Field>() {
@Override
public Field run() {
Field field;
try {
field = ComponentModule.class.getDeclaredField("lockManager");
field.setAccessible(true);
return field;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
});
/**
* Returns the lock manager for the given ComponentModule
*
* @param module
* @return
*/
static ThreadLockManager getLockManager(final ComponentModule module) {
try {
if (lockManager_field != null) {
return (ThreadLockManager) lockManager_field.get(module);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Helper for WrappedCallable/WrappedRunnable
*
* @param ctx
* @param mcl
* @param wrapper
* @throws ServletException
*/
static void initModule(final NotesContext ctx, final ClassLoader mcl, final Object wrappedObject) throws ServletException {
// next initialize the context with a FakeHttpRequest. This is neccessary so that internal things
// like session-creation and so on work properly
// RPr:
// You may ask what I do here: ReLoading the module again from the MCL triggers signature checking.
if (mcl instanceof ModuleClassLoader) {
URLClassLoader dcl = (URLClassLoader) ((ModuleClassLoader) mcl).getDynamicClassLoader();
String className = wrappedObject.getClass().getName();
String str = className.replace('.', '/') + ".class";
URL url = dcl.findResource(str);
if (url != null && url.getProtocol().startsWith("xspnsf")) {
// Set up the "TopLevelXPageSigner == Signer of the runnable
// As soon as we are in a xspnsf, we do not have a SessionFactory!
ctx.setSignerSessionRights("WEB-INF/classes/" + str);
// RPr: There is a bug: you can decide if you want to use "sessionAsSigner" or "sessionAsSignerFullAccess"
// But you cannot use both simultaneously!
Session signerSession = ctx.getSessionAsSigner(true);
if (signerSession != null) {
Factory.setSessionFactory(new XPageSignerSessionFactory(false), SessionType.SIGNER);
Factory.setSessionFactory(new XPageSignerSessionFactory(true), SessionType.SIGNER_FULL_ACCESS);
} else {
// do not setup signer sessions if it is not properly signed!
Factory.setSessionFactory(new InvalidSessionFactory(), SessionType.SIGNER);
Factory.setSessionFactory(new InvalidSessionFactory(), SessionType.SIGNER_FULL_ACCESS);
}
} else {
// The code is not part from an NSF, so it resides on the server
Factory.setSessionFactory(new XPageNamedSessionFactory(Factory.getLocalServerName(), false), SessionType.SIGNER);
Factory.setSessionFactory(new XPageNamedSessionFactory(Factory.getLocalServerName(), true), SessionType.SIGNER_FULL_ACCESS);
}
}
Factory.setClassLoader(mcl);
}
// ------------------------------
protected NSFComponentModule getCurrentModule() {
NotesContext ctx = NotesContext.getCurrentUnchecked();
if (ctx == null)
return null;
return ctx.getModule();
}
@Override
protected <V> IWrappedCallable<V> wrap(final Callable<V> inner) {
if (inner instanceof IWrappedCallable)
return (IWrappedCallable<V>) inner;
NSFComponentModule module = getCurrentModule();
Tasklet annot = inner.getClass().getAnnotation(Tasklet.class);
if (annot == null || annot.context() == Context.DEFAULT) {
if (module == null) {
return super.wrap(inner);
} else {
if (inner instanceof AbstractXotsCallable) {
XotsContext ctx = new XotsContext();
ctx.setOpenLogApiPath(ODAPlatform.getXspPropertyAsString("xsp.openlog.filepath"));
ctx.setContextApiPath(module.getDatabasePath());
ctx.setTaskletClass(inner.getClass().getName());
((AbstractXotsCallable) inner).setContext(ctx);
}
return new XotsWrappedCallable<V>(module, inner);
}
} else if (annot.context() == Context.PLUGIN) {
return super.wrap(inner);
} else if (annot.context() == Context.XSPSCOPED || annot.context() == Context.XSPBARE) {
if (inner instanceof AbstractXotsXspCallable) {
XotsXspContext ctx = new XotsXspContext();
if (annot.context() == Context.XSPSCOPED) {
ctx.initialiseXspContext(true);
} else {
ctx.initialiseXspContext(false);
}
if (module != null) {
ctx.setOpenLogApiPath(ODAPlatform.getXspPropertyAsString("xsp.openlog.filepath"));
ctx.setContextApiPath(module.getDatabasePath());
ctx.setTaskletClass(inner.getClass().getName());
}
((AbstractXotsXspCallable) inner).setContext(ctx);
}
if (module == null) {
return super.wrap(inner);
} else {
return new XotsWrappedCallable(module, inner);
}
} else {
if (module == null) {
throw new NullPointerException("No module is running");
}
return new XotsWrappedCallable<V>(module, inner);
}
}
@Override
protected IWrappedRunnable wrap(final Runnable inner) {
if (inner instanceof IWrappedRunnable)
return (IWrappedRunnable) inner;
NSFComponentModule module = getCurrentModule();
Tasklet annot = inner.getClass().getAnnotation(Tasklet.class);
if (annot == null || annot.context() == Context.DEFAULT) {
if (module == null) {
return super.wrap(inner);
} else {
if (inner instanceof AbstractXotsRunnable) {
XotsContext ctx = new XotsContext();
ctx.setOpenLogApiPath(ODAPlatform.getXspPropertyAsString("xsp.openlog.filepath"));
ctx.setContextApiPath(module.getDatabasePath());
ctx.setTaskletClass(inner.getClass().getName());
((AbstractXotsRunnable) inner).setContext(ctx);
}
return new XotsWrappedRunnable(module, inner);
}
} else if (annot.context() == Context.PLUGIN) {
return super.wrap(inner);
} else if (annot.context() == Context.XSPSCOPED || annot.context() == Context.XSPBARE) {
if (inner instanceof AbstractXotsXspRunnable) {
XotsXspContext ctx = new XotsXspContext();
if (annot.context() == Context.XSPSCOPED) {
ctx.initialiseXspContext(true);
} else {
ctx.initialiseXspContext(false);
}
if (module != null) {
ctx.setOpenLogApiPath(ODAPlatform.getXspPropertyAsString("xsp.openlog.filepath"));
ctx.setContextApiPath(module.getDatabasePath());
ctx.setTaskletClass(inner.getClass().getName());
}
((AbstractXotsXspRunnable) inner).setContext(ctx);
}
if (module == null) {
return super.wrap(inner);
} else {
return new XotsWrappedRunnable(module, inner);
}
} else {
if (module == null) {
throw new NullPointerException("No module is running");
}
return new XotsWrappedRunnable(module, inner);
}
}
@Override
protected IWrappedCallable<?> wrap(final String moduleName, final String className, final Object... ctorArgs) {
if (moduleName.startsWith("bundle:")) {
return new XotsBundleTasklet(moduleName.substring(7), className, ctorArgs);
}
return new XotsModuleTasklet(moduleName, className, ctorArgs);
}
}