/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.eas.server;
import com.eas.client.Application;
import com.eas.client.DatabasesClient;
import com.eas.client.ModulesProxy;
import com.eas.client.ScriptedDatabasesClient;
import com.eas.client.ServerModulesProxy;
import com.eas.client.SqlQuery;
import com.eas.client.cache.ApplicationSourceIndexer;
import com.eas.client.cache.FormsDocuments;
import com.eas.client.cache.ModelsDocuments;
import com.eas.client.cache.PlatypusFiles;
import com.eas.client.cache.ReportsConfigs;
import com.eas.client.cache.ScriptsConfigs;
import com.eas.client.cache.ScriptDocument;
import com.eas.client.cache.ScriptDocument.ModuleDocument;
import com.eas.client.login.PlatypusPrincipal;
import com.eas.client.queries.ContextHost;
import com.eas.client.queries.QueriesProxy;
import com.eas.client.scripts.ScriptedResource;
import com.eas.script.HasPublished;
import com.eas.script.JsDoc;
import com.eas.script.Scripts;
import com.eas.server.handlers.ServerModuleStructureRequestHandler;
import com.eas.server.handlers.RPCRequestHandler;
import com.eas.script.JsObjectException;
import com.eas.script.SystemJSCallback;
import java.io.File;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import jdk.nashorn.api.scripting.JSObject;
import jdk.nashorn.internal.runtime.Undefined;
/**
* The core class for platypus server infrastructure (e.g. Standalone J2SE
* server and J2EE servlets).
*
* @author mg
*/
public abstract class PlatypusServerCore implements ContextHost, Application<SqlQuery> {
protected String startModuleName;
protected final Scripts.Space[] statelessSpaces;
protected SessionManager sessionManager;
protected ScriptedDatabasesClient basesProxy;
protected ApplicationSourceIndexer indexer;
protected ModulesProxy modules;
protected QueriesProxy<SqlQuery> queries;
protected ScriptsConfigs scriptsConfigs;
protected FormsDocuments forms = new FormsDocuments();
protected ReportsConfigs reports = new ReportsConfigs();
protected ModelsDocuments models = new ModelsDocuments();
protected ServerModulesProxy localServerModules = new LocalServerModulesProxy(this);
protected Scripts.Space queueSpace;
public PlatypusServerCore(ApplicationSourceIndexer aIndexer, ModulesProxy aModules, QueriesProxy<SqlQuery> aQueries, ScriptedDatabasesClient aDatabasesClient, ScriptsConfigs aSecurityConfigs, String aDefaultAppElement) throws Exception {
this(aIndexer, aModules, aQueries, aDatabasesClient, aSecurityConfigs, aDefaultAppElement, new SessionManager(), (Runtime.getRuntime().availableProcessors() + 1) * 10);
}
public PlatypusServerCore(ApplicationSourceIndexer aIndexer, ModulesProxy aModules, QueriesProxy<SqlQuery> aQueries, ScriptedDatabasesClient aDatabasesClient, ScriptsConfigs aSecurityConfigs, String aDefaultAppElement, SessionManager aSessionManager, int aMaxStatelessSpaces) throws Exception {
super();
indexer = aIndexer;
modules = aModules;
queries = aQueries;
basesProxy = aDatabasesClient;
sessionManager = aSessionManager;
startModuleName = aDefaultAppElement;
scriptsConfigs = aSecurityConfigs;
queueSpace = Scripts.createQueue();
statelessSpaces = new Scripts.Space[Math.max(1, aMaxStatelessSpaces)];
for (int s = 0; s < statelessSpaces.length; s++) {
statelessSpaces[s] = Scripts.createSpace();
}
}
public Scripts.Space getQueueSpace() {
return queueSpace;
}
public ApplicationSourceIndexer getIndexer() {
return indexer;
}
@Override
public ModulesProxy getModules() {
return modules;
}
@Override
public QueriesProxy<SqlQuery> getQueries() {
return queries;
}
@Override
public ScriptsConfigs getScriptsConfigs() {
return scriptsConfigs;
}
@Override
public ModelsDocuments getModels() {
return models;
}
@Override
public ReportsConfigs getReports() {
return reports;
}
@Override
public FormsDocuments getForms() {
return forms;
}
@Override
public ServerModulesProxy getServerModules() {
return localServerModules;
}
public SessionManager getSessionManager() {
return sessionManager;
}
public DatabasesClient getDatabasesClient() {
return basesProxy;
}
public String getStartModuleName() {
return startModuleName;
}
public int getMaxStatelessSpaces() {
return statelessSpaces.length;
}
/**
* Executes a script module according to all rules defimed within
* Platypus.js Such as @stateless and @rezident annotations, async-io
* convensions etc.
*
* @param aModuleName
* @param aMethodName
* @param aArguments
* @param aNetworkRPC
* @param aOnSuccess
* @param aOnFailure
*/
public void executeMethod(String aModuleName, String aMethodName, Object[] aArguments, boolean aNetworkRPC, Consumer<Object> aOnSuccess, Consumer<Exception> aOnFailure) {
Scripts.LocalContext callingContext = Scripts.getContext();
Scripts.Space callingSpace = Scripts.getSpace();
Object[] copiedArguments = makeArgumentsCopy(callingSpace, aArguments);
Consumer<Object> onSuccess = (Object res) -> {
if (aOnSuccess != null) {
Scripts.Space targetSpace = Scripts.getSpace();
Object copiedRes = targetSpace.makeCopy(res);
callingSpace.process(callingContext, () -> {
assert Scripts.getSpace() == callingSpace;
aOnSuccess.accept(callingSpace.restoreCopy(copiedRes));
});
}
};
Consumer<Exception> onFailure = (Exception ex) -> {
if (aOnFailure != null) {
Scripts.Space targetSpace = Scripts.getSpace();
Exception copiedEx = ex instanceof JsObjectException ? new JsObjectException(targetSpace.makeCopy(((JsObjectException) ex).getData())) : ex;
callingSpace.process(callingContext, () -> {
assert Scripts.getSpace() == callingSpace;
Exception restoredEx = copiedEx instanceof JsObjectException ? new JsObjectException(callingSpace.restoreCopy(((JsObjectException) copiedEx).getData())) : copiedEx;
aOnFailure.accept(restoredEx);
});
}
};
if (aModuleName == null || aModuleName.isEmpty()) {
onFailure.accept(new Exception("Module name is missing. Unnamed server modules are not allowed."));
} else if (aMethodName == null || aMethodName.isEmpty()) {
onFailure.accept(new Exception("Module's method name is missing."));
} else {
Consumer<ModuleDocument> withConfig = (ModuleDocument config) -> {
try {
if (!aNetworkRPC || config.hasAnnotation(JsDoc.Tag.PUBLIC_TAG)) {
// Let's perform security checks
ServerModuleStructureRequestHandler.checkPrincipalPermission(config.getAllowedRoles(), aModuleName);
Scripts.Space targetSpace;
Session targetSession;
if (config.hasAnnotation(JsDoc.Tag.RESIDENT_TAG)) {
targetSession = sessionManager.getSystemSession();
targetSpace = targetSession.getSpace();
} else if (config.hasAnnotation(JsDoc.Tag.STATELESS_TAG)) {
targetSession = null;
int rnd = new Random().nextInt(statelessSpaces.length);
targetSpace = statelessSpaces[rnd];
} else {// Statefull session module
targetSession = (Session) callingContext.getSession();
targetSpace = targetSession.getSpace();
}
Scripts.LocalContext targetContext = new Scripts.LocalContext(callingContext.getRequest(), callingContext.getResponse(), callingContext.getPrincipal(), callingContext.getSession());
targetSpace.process(targetContext, () -> {
assert Scripts.getSpace() == targetSpace;
try {
Consumer<JSObject> withModuleConstructor = (JSObject constr) -> {
assert Scripts.getSpace() == targetSpace;
try {
JSObject moduleInstance;
if (targetSession == null || !targetSession.containsModule(aModuleName)) {
if (constr != null) {
moduleInstance = (JSObject) constr.newObject(new Object[]{});
if (targetSession != null) {
targetSession.registerModule(aModuleName, moduleInstance);
}
} else {
throw new IllegalArgumentException(String.format(RPCRequestHandler.MODULE_MISSING_OR_NOT_A_MODULE, aModuleName));
}
} else {
moduleInstance = targetSession.getModule(aModuleName);
}
if (moduleInstance != null) {
Logger.getLogger(PlatypusServerCore.class.getName()).log(Level.FINE, RPCRequestHandler.EXECUTING_METHOD_TRACE_MSG, new Object[]{aMethodName, aModuleName});
Object oFun = moduleInstance.getMember(aMethodName);
if (oFun instanceof JSObject && ((JSObject) oFun).isFunction()) {
AtomicBoolean executed = new AtomicBoolean();
List<Object> arguments = new ArrayList<>();
for (Object argument : copiedArguments) {
arguments.add(targetSpace.restoreCopy(argument));
}
arguments.add(new SystemJSCallback() {
@Override
public Object call(final Object thiz, final Object... largs) {
if (!aNetworkRPC || !executed.get()) {
executed.set(true);
Object returned = largs.length > 0 ? largs[0] : null;
onSuccess.accept(returned);// WARNING! Don't insert .toJava() because of RPC handler
} else {
Logger.getLogger(RPCRequestHandler.class.getName()).log(Level.WARNING, RPCRequestHandler.BOTH_IO_MODELS_MSG, new Object[]{aMethodName, aModuleName});
}
return null;
}
});
arguments.add(new SystemJSCallback() {
@Override
public Object call(final Object thiz, final Object... largs) {
if (!aNetworkRPC || !executed.get()) {
executed.set(true);
Object reason = largs.length > 0 ? largs[0] : null;
if (reason instanceof Exception) {
onFailure.accept((Exception) reason);// WARNING! Don't insert .toJava() because of RPC handler
} else {
onFailure.accept(new JsObjectException(reason));
}
} else {
Logger.getLogger(RPCRequestHandler.class.getName()).log(Level.WARNING, RPCRequestHandler.BOTH_IO_MODELS_MSG, new Object[]{aMethodName, aModuleName});
}
return null;
}
});
Scripts.getContext().initAsyncs(0);
try {
ServerModuleStructureRequestHandler.checkPrincipalPermission(config.getPropertyAllowedRoles().get(aMethodName), aModuleName + "." + aMethodName);
Object result = ((JSObject) oFun).call(moduleInstance, arguments.toArray());
int asyncs = Scripts.getContext().getAsyncsCount();
if (!(result instanceof Undefined) || asyncs == 0) {
if (!executed.get()) {
executed.set(true);
onSuccess.accept(result);// WARNING! Don't insert .toJava() because of RPC handler
} else {
Logger.getLogger(RPCRequestHandler.class.getName()).log(Level.WARNING, RPCRequestHandler.BOTH_IO_MODELS_MSG, new Object[]{aMethodName, aModuleName});
}
}
} finally {
Scripts.getContext().initAsyncs(null);
}
} else {
throw new Exception(String.format(RPCRequestHandler.METHOD_MISSING_MSG, aMethodName, aModuleName));
}
} else {
throw new Exception(String.format(RPCRequestHandler.MODULE_MISSING_MSG, aModuleName));
}
} catch (Exception ex) {
onFailure.accept(ex);
}
};
JSObject moduleConstructor = targetSpace.lookup(aModuleName);
if (moduleConstructor != null) {
withModuleConstructor.accept(moduleConstructor);
} else {
ScriptedResource._require(new String[]{aModuleName}, null, targetSpace, new HashSet<>(), (Void v) -> {
assert Scripts.getSpace() == targetSpace;
assert Scripts.getContext() == targetContext;
withModuleConstructor.accept(targetSpace.lookup(aModuleName));
}, (Exception ex) -> {
onFailure.accept(ex);
});
}
} catch (Exception ex) {
onFailure.accept(ex);
}
});
} else {
throw new AccessControlException(String.format("Public access to module %s is denied.", aModuleName));//NOI18N
}
} catch (AccessControlException ex) {
onFailure.accept(ex);
}
};
try {
ScriptDocument.ModuleDocument moduleDoc = lookupModuleDocument(aModuleName);
if (moduleDoc != null) {
withConfig.accept(moduleDoc);
} else {
throw new IllegalArgumentException(String.format(RPCRequestHandler.MODULE_MISSING_OR_NOT_A_MODULE, aModuleName));
}
} catch (Exception ex) {
onFailure.accept(ex);
}
}
}
public ModuleDocument lookupModuleDocument(String aModuleName) throws Exception {
File indexedFile = indexer.nameToFile(aModuleName);
if (indexedFile != null && indexedFile.getName().endsWith(PlatypusFiles.JAVASCRIPT_FILE_END)) {
String defaultModuleName = indexer.getDefaultModuleName(indexedFile);
ScriptDocument scriptDoc = scriptsConfigs.getCachedConfig(defaultModuleName);
if (scriptDoc == null) {
scriptDoc = scriptsConfigs.get(defaultModuleName, indexedFile);
}
if (scriptDoc != null) {
return scriptDoc.getModules().get(aModuleName);
} else {
throw new IllegalArgumentException(String.format(RPCRequestHandler.MODULE_MISSING_OR_NOT_A_MODULE, aModuleName));
}
} else {
throw new IllegalArgumentException(String.format(RPCRequestHandler.MODULE_MISSING_OR_NOT_A_MODULE, aModuleName));
}
}
public Object[] makeArgumentsCopy(Scripts.Space aSpace, Object[] aArguments) {
Object[] arguments = Arrays.copyOf(aArguments, aArguments.length);
for (int a = 0; a < arguments.length; a++) {
if (arguments[a] instanceof HasPublished) {
arguments[a] = ((HasPublished) arguments[a]).getPublished();
} else {
arguments[a] = aSpace.makeCopy(arguments[a]);
}
}
return arguments;
}
@Override
public String preparationContext() throws Exception {
Scripts.LocalContext context = Scripts.getContext();
if (context != null && context.getPrincipal() != null) {
return ((PlatypusPrincipal) context.getPrincipal()).getContext();
} else {
return null;
}
}
@Override
public String unpreparationContext() throws Exception {
return basesProxy.getMetadataCache(null).getDatasourceSchema();
}
}