package org.openntf.domino.xsp;
import java.util.List;
import java.util.concurrent.Callable;
import org.openntf.domino.AutoMime;
import org.openntf.domino.View;
import org.openntf.domino.config.Configuration;
import org.openntf.domino.config.ServerConfiguration;
import org.openntf.domino.exceptions.BackendBridgeSanityCheckException;
import org.openntf.domino.ext.Session.Fixes;
import org.openntf.domino.session.INamedSessionFactory;
import org.openntf.domino.thread.DominoExecutor;
import org.openntf.domino.utils.Factory;
import org.openntf.domino.utils.Factory.ThreadConfig;
import org.openntf.domino.xots.Xots;
import org.openntf.domino.xsp.helpers.OsgiServiceLocatorFactory;
import org.openntf.domino.xsp.session.XPageNamedSessionFactory;
import org.openntf.domino.xsp.xots.XotsDominoExecutor;
import org.openntf.service.ServiceLocatorFinder;
import com.ibm.commons.Platform;
import com.ibm.commons.extension.ExtensionManager;
import com.ibm.commons.util.StringUtil;
import com.ibm.designer.runtime.Application;
import com.ibm.domino.napi.c.BackendBridge;
public enum ODAPlatform {
;
// TODO: create an OSGI-command
public static final boolean _debug = false;
public static final boolean debugAll = false;
public static boolean isStarted_ = false;
private static int xotsStopDelay;
public synchronized static boolean isStarted() {
return isStarted_;
}
/**
* Start up the ODAPlatform.
*
* <ol>
* <li>configure the {@link ServiceLocatorFinder}</li>
* <li>startup the {@link Factory}</li>
* <li>Configure the Factory with proper {@link INamedSessionFactory}s for XPages</li>
* <li>Call {@link #verifyIGetEntryByKey()}</li>
* <li>Start the {@link Xots} with 10 threads (if it is not completely disabled with "XotsTasks=0" in oda.nsf)</li>
* </ol>
*
* This is done automatically on server start or can manually invoked with<br>
* <code>tell http osgi oda start</code><br>
* on the server console.
*/
public synchronized static void start() {
if (!isStarted()) {
isStarted_ = true;
// Here is all the init/term stuff done
ServiceLocatorFinder.setServiceLocatorFactory(new OsgiServiceLocatorFactory());
Factory.startup();
// Setup the named factories 4 XPages
Factory.setNamedFactories4XPages(new XPageNamedSessionFactory(false), new XPageNamedSessionFactory(true));
verifyIGetEntryByKey();
ServerConfiguration cfg = Configuration.getServerConfiguration();
int xotsTasks = cfg.getXotsTasks();
// We must read the value here, because in the ShutDown, it is not possible to navigate through views and the code will fail.
xotsStopDelay = cfg.getXotsStopDelay();
if (xotsTasks > 0) {
DominoExecutor executor = new XotsDominoExecutor(xotsTasks);
try {
Xots.start(executor);
} catch (IllegalStateException e) {
if (isDebug()) {
throw e;
}
}
List<?> tasklets = ExtensionManager.findServices(null, ODAPlatform.class, "org.openntf.domino.xots.tasklet");
for (Object tasklet : tasklets) {
if (tasklet instanceof Callable<?> || tasklet instanceof Runnable) {
@SuppressWarnings("unused")
ClassLoader cl = tasklet.getClass().getClassLoader();
Factory.println("XOTS", "Registering tasklet " + tasklet);
if (tasklet instanceof Callable<?>) {
Xots.getService().submit((Callable<?>) tasklet);
} else {
Xots.getService().submit((Runnable) tasklet);
}
}
}
}
}
}
/**
* Stops the ODA Platform and tries to kill all running Xots Tasks.
*
* This is done automatically on server shutdown or can manually invoked with<br>
* <code>tell http osgi oda stop</code><br>
* on the server console.
*/
public synchronized static void stop() {
if (isStarted()) {
if (Xots.isStarted()) {
Xots.stop(xotsStopDelay);
}
Factory.shutdown();
isStarted_ = false;
}
}
/**
* there is one weird thing in getViewEntryByKeyWithOptions. IBM messed up something in the JNI calls.
*
* a correct call would look like this:
*
* <pre>
* jclass activityClass = env -> GetObjectClass(dummyView);
* jmethodID mID = env -> GetMethodID(activityClass, "iGetEntryByKey", "...");
* entry = env -> CallIntMethod(obj, mID);
* </pre>
*
* IBM's code probably looks like this:
*
* <pre>
* jclass activityClass = env->GetObjectClass(lotus.domino.local.View); <font color=red><--- This is wrong!</font>
* jmethodID mID = env->GetMethodID(activityClass, "iGetEntryByKey", "...");
* entry = env->CallIntMethod(obj, mID);
* </pre>
*
* so we get the method-pointer mID for the "lotus.domino.local.View" and we call this method on an "org.openntf.domino.impl.View".
*
* This is something that normally wouldn't work. But C/C++ does no sanity checks if it operates on the correct class and will call a
* (more or less) random method that is on position "mID". (compare to a 'goto 666')
*
* To get that working, we must reorder the methods in the View class, so that "iGetEntryByKey" is on the correct place. Every time you
* add or remove methods to the View class (and maybe also to the Base class) the position must be checked again. This is done in the
* this method:
* <ol>
* <li>We call getViewEntryByKeyWithOptions with the "key parameters" dummyView, null, 42.</li>
* <li>This will result in a call to dummyView.iGetEntryByKey(null, false, 42);</li>
* <li>If iGetEntryByKey is called with a "null" vector and 42 as int, it will throw a "BackendBridgeSanityCheckException" (which we
* expect)</li>
* <li>If any other method is called it will throw a different exception. (Most likely a NPE, because our view has no delegate)</li>
* </ol>
* I hope the server would not crash then. I assume this because:
* <ul>
* <li>null as parameter is less problematic than a Vector that was forced in a String variable</li>
* <li>Throwing an exception does not generate a return value that will be forced in a ViewEntry</li>
* </ul>
*/
private static void verifyIGetEntryByKey() {
@SuppressWarnings("deprecation")
View dummyView = new org.openntf.domino.impl.View();
try {
BackendBridge.getViewEntryByKeyWithOptions(dummyView, null, 42);
} catch (BackendBridgeSanityCheckException allGood) {
return;
} catch (Exception e) {
e.printStackTrace();
// if you get here, analyze the stack trace and rearrange the "iGetEntryByKey" method in
// the view to the position that is listed in the stack trace above "getViewEntryByKeyWithOptions"
}
// if you do not get an exception, you will have to debug it with "step into"
Factory.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
Factory.println("Operation of BackendBridge.getViewEntryByKeyWithOptions FAILED");
Factory.println("Please read the comments in " + ODAPlatform.class.getName());
Factory.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
}
/**
* Gets a property or notes.ini variable, based on the value passed Order of execution is:
* <ol>
* <li>Platform.getProperty()</li>
* <li>System.getProperty()</li>
* <li>Os.OSGetEnvironmentString() = notes.ini</li>
* </ol>
*
* @param propertyName
* String property to check for
* @return String value for the property
* @since org.openntf.domino.xsp 5.0.0
*/
public static String getEnvironmentString(final String propertyName) {
String result = "";
try {
result = Platform.getInstance().getProperty(propertyName);
if (StringUtil.isEmpty(result)) {
result = System.getProperty(propertyName);
if (StringUtil.isEmpty(result)) {
result = com.ibm.xsp.model.domino.DominoUtils.getEnvironmentString(propertyName);
}
}
} catch (Throwable t) {
t.printStackTrace();
}
return result;
}
/**
* Gets a notes.ini property, splitting on commas
*
* @param propertyName
* String to look up on
* @return String[] of values for the given property, split on commas
* @since org.openntf.domino 5.0.0
*/
public static String[] getEnvironmentStrings(final String propertyName) {
try {
String setting = getEnvironmentString(propertyName);
if (StringUtil.isNotEmpty(setting)) {
if (StringUtil.indexOfIgnoreCase(setting, ",") > -1) {
return StringUtil.splitString(setting, ',');
} else {
return new String[] { setting };
}
}
} catch (Throwable t) {
t.printStackTrace();
}
return new String[0];
}
/**
* Gets an Xsp property or notes.ini variable for PLUGIN_ID (="org.openntf.domino.xsp")
*
* @return String value for the PLUGIN_ID property
* @since org.openntf.domino.xsp 2.5.0
*/
public static String getEnvironmentString() {
return getEnvironmentString(Activator.PLUGIN_ID);
}
/**
* Gets an Xsp property or notes.ini variable for PLUGIN_ID (="org.openntf.domino.xsp"), splitting on commas
*
* @return String[] of values for the given property, split on commas
* @since org.openntf.domino 5.0.0
*/
public static String[] getEnvironmentStrings() {
return getEnvironmentStrings(Activator.PLUGIN_ID);
}
/**
* Gets an Xsp Property, returning just the basic string as seen in the xsp.properties file. Order of execution is:
* <ol>
* <li>xsp.properties in NSF</li>
* <li>Platform.getProperty()</li>
* <li>System.getProperty()</li>
* <li>Os.OSGetEnvironmentString() = notes.ini</li>
* </ol>
*
* @param app
* the Application to use (or null for current one)
* @param propertyName
* Property name to look for
* @return String property value or an empty string
* @since org.openntf.domino.xsp 4.5.0
*/
public static String getXspPropertyAsString(final String propertyName, Application app) {
String result = "";
try {
if (app == null)
app = Application.get();
if (app == null) {
result = getEnvironmentString(propertyName);
} else {
result = app.getProperty(propertyName);
if (StringUtil.isEmpty(result)) {
result = getEnvironmentString(propertyName);
}
}
} catch (Throwable t) {
t.printStackTrace();
}
return result;
}
/**
* Gets an Xsp property. Order of execution is:
* <ol>
* <li>xsp.properties in NSF</li>
* <li>Platform.getProperty()</li>
* <li>System.getProperty()</li>
* <li>Os.OSGetEnvironmentString() = notes.ini</li>
* </ol>
*
* @param app
* the Application to use (or null for current one)
* @param propertyName
* Property name to look for
* @return String array of property, split on commas
* @since org.openntf.domino 2.5.0
*/
public static String[] getXspProperty(final String propertyName, final Application app) {
try {
String setting = getXspPropertyAsString(propertyName, app);
if (StringUtil.isNotEmpty(setting)) {
if (StringUtil.indexOfIgnoreCase(setting, ",") > -1) {
return StringUtil.splitString(setting, ',');
} else {
return new String[] { setting };
}
}
} catch (Throwable t) {
t.printStackTrace();
}
return new String[0];
}
private static String IS_API_ENABLED = "ODAPlatform.isAPIEnabled";
/**
* Checks whether or not the API is enabled for the current database
*
* @param ctx
* the current Application (if none specified, the current is used)
* @return boolean whether or not enabled
* @since org.openntf.domino.xsp 2.5.0
*/
public static boolean isAPIEnabled(Application app) {
if (app == null)
app = Application.get();
if (app == null)
return false;
Boolean retVal_ = (Boolean) app.getObject(IS_API_ENABLED);
if (retVal_ == null) {
retVal_ = Boolean.FALSE;
for (String s : getXspProperty("xsp.library.depends", app)) {
if (s.equalsIgnoreCase(XspLibrary.LIBRARY_ID)) {
retVal_ = Boolean.TRUE;
break;
}
}
app.putObject(IS_API_ENABLED, retVal_);
}
return retVal_.booleanValue();
}
/**
* common code to test if a flag is set in the xsp.properties file for the "org.openntf.domino.xsp" value.
*
* @param app
* the Application (or null for current one)
* @param flagName
* use upperCase for flagName, e.g. RAID
* @return true if the flag is set
*/
public static boolean isAppFlagSet(final String flagName, Application app) {
if (app == null)
app = Application.get();
if (app == null)
return false;
String key = "ODAPlatform.flag." + flagName;
Boolean retVal_ = (Boolean) app.getObject(key);
if (retVal_ == null) {
retVal_ = Boolean.FALSE;
for (String s : getXspProperty(Activator.PLUGIN_ID, app)) {
if (s.equalsIgnoreCase(flagName)) {
retVal_ = Boolean.TRUE;
break;
}
}
app.putObject(key, retVal_);
}
return retVal_.booleanValue();
}
private static String GET_AUTO_MIME = "ODAPlatform.getAutoMime";
/**
* Gets the AutoMime option enabled for the application, an instance of the enum {@link AutoMime}
*
* @param ctx
* FacesContext
* @return AutoMime
* @since org.openntf.domino.xsp 5.0.0
*/
public static AutoMime getAppAutoMime(Application app) {
if (app == null)
app = Application.get();
if (app == null)
return AutoMime.WRAP_ALL;
AutoMime retVal_ = (AutoMime) app.getObject(GET_AUTO_MIME);
if (retVal_ == null) {
retVal_ = AutoMime.WRAP_ALL;
for (String s : getXspProperty(Activator.PLUGIN_ID, app)) {
if (s.equalsIgnoreCase("automime32k")) {
retVal_ = AutoMime.WRAP_32K;
break;
} else if (s.equalsIgnoreCase("automimenone")) {
retVal_ = AutoMime.WRAP_NONE;
break;
}
}
app.putObject(GET_AUTO_MIME, retVal_);
}
return retVal_;
}
/**
* Gets whether the khan flag is enabled for the application
*
* @param ctx
* FacesContext
* @return boolean
* @since org.openntf.domino.xsp 3.0.0
*/
public static boolean isAppAllFix(final Application app) {
return isAppFlagSet("KHAN", app);
}
/**
* Gets whether the raid flag is enabled for the application
*
* @param ctx
* FacesContext
* @return boolean
* @since org.openntf.domino.xsp 3.0.0
*/
public static boolean isAppDebug(final Application app) {
if (debugAll)
return true;
return isAppFlagSet("RAID", app);
}
/**
* Gets whether the godmode flag is enabled for the application
*
* @param ctx
* FacesContext
* @return boolean
* @since org.openntf.domino.xsp 3.0.0
*/
public static boolean isAppGodMode(final Application app) {
return isAppFlagSet("GODMODE", app);
}
/**
* Gets whether the marcel flag is enabled for the application
*
* @param ctx
* FacesContext
* @return boolean
* @since org.openntf.domino.xsp 3.0.0
*/
public static boolean isAppMimeFriendly(final Application app) {
return isAppFlagSet("MARCEL", app);
}
/**
* Whether or not the library is running in debug. In debug, messages are written to the server console
*
* @return boolean debug or not
* @since org.openntf.domino.xsp 2.5.0
*/
public static boolean isDebug() {
return _debug;
}
public static String getXspPropertyAsString(final String propertyName) {
return getXspPropertyAsString(propertyName, null);
}
public static boolean isAPIEnabled() {
return isAPIEnabled(null);
}
public static boolean isAppFlagSet(final String flagName) {
return isAppFlagSet(flagName, null);
}
public static ThreadConfig getAppThreadConfig(final Application app) {
Fixes[] fixes = isAppFlagSet("KHAN", app) ? Fixes.values() : null;
AutoMime autoMime = getAppAutoMime(app);
boolean bubbleExceptions = ODAPlatform.isAppFlagSet("BUBBLEEXCEPTIONS");
return new ThreadConfig(fixes, autoMime, bubbleExceptions);
}
}