/*
ESXX - The friendly ECMAscript/XML Application Server
Copyright (C) 2007-2015 Martin Blom <martin@blom.org>
This program is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.esxx.js;
import org.esxx.ESXX;
import org.esxx.ESXXException;
import org.esxx.Application;
import java.io.InputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import org.mozilla.javascript.*;
public class JSESXX
extends ScriptableObject {
private static final long serialVersionUID = -7401965682230734380L;
public JSESXX() {
super();
}
public JSESXX(Context cx, Scriptable scope, Application app) {
this();
this.app = app;
}
@Override
public String getClassName() {
return "ESXX";
}
public static Object jsConstructor(Context cx,
java.lang.Object[] args,
Function ctorObj,
boolean inNewExpr) {
return new JSESXX(cx, ctorObj, (Application) args[0]);
}
public static void finishInit(Scriptable scope,
FunctionObject constructor,
Scriptable prototype) {
// Define classes in constructor object
try {
ScriptableObject.defineClass(constructor, JSLogger.class);
ScriptableObject.defineClass(constructor, JSLRUCache.class);
ScriptableObject.defineClass(constructor, JSRequest.class);
ScriptableObject.defineClass(constructor, JSResponse.class);
ScriptableObject.defineClass(constructor, JSSchema.class);
ScriptableObject.defineClass(constructor, JSStylesheet.class);
}
catch (Exception ex) {
throw new ESXXException("Failed to define Logger, Request and Response classes");
}
}
public static Scriptable newObject(Context cx, Scriptable scope, String name, Object[] args) {
Scriptable global = getTopLevelScope(scope);
Scriptable esxx = (Scriptable) global.get("ESXX", global);
Function ctor = (Function) esxx.get(name, esxx);
return ctor.construct(cx, scope, args);
}
public Synchronizer jsFunction_sync(Function f) {
return new Synchronizer(f);
}
public void jsFunction_notify(Object o) {
synchronized (o) {
o.notify();
}
}
public void jsFunction_notifyAll(Object o) {
synchronized (o) {
o.notifyAll();
}
}
public static boolean jsFunction_wait(Context cx, Scriptable thisObj,
Object[] args, Function funcObj) {
if (args.length < 1 || args[0] == Context.getUndefinedValue()) {
throw Context.reportRuntimeError("Required argument missing.");
}
Object object = args[0];
Function func = null;
long timeout_ms = 0;
if (args.length >= 2) {
if (!(args[1] instanceof Number)) {
throw Context.reportRuntimeError("Third argument must be a number.");
}
timeout_ms = (long) (1000 * Context.toNumber(args[1]));
}
if (args.length >= 3) {
if (!(args[2] instanceof Function)) {
throw Context.reportRuntimeError("Third argument must be a function.");
}
func = (Function) args[2];
}
long now = System.currentTimeMillis();
long expires = timeout_ms != 0 ? now + timeout_ms : Long.MAX_VALUE;
Object[] fargs = new Object[] { object };
boolean lastrc = false;
synchronized (object) {
if (func == null) {
// If not specified in JS, timeout_ms == 0 which is the same as o.wait().
try {
object.wait(timeout_ms);
}
catch (InterruptedException ex) {
ESXX.checkTimeout(cx);
}
}
else {
Scriptable thiz = func.getParentScope();
while (now < expires &&
(lastrc = Context.toBoolean(func.call(cx, thiz, thiz, fargs))) == false) {
try {
object.wait(expires - now);
}
catch (InterruptedException ex) {
ESXX.checkTimeout(cx);
}
now = System.currentTimeMillis();
}
}
}
return lastrc;
}
public static void jsFunction_include(Context cx, Scriptable thisObj,
Object[] args, Function funcObj)
throws IOException {
ESXX esxx = ESXX.getInstance();
JSESXX js_esxx = (JSESXX) thisObj;
Scriptable scope = ScriptableObject.getTopLevelScope(thisObj);
Application app = js_esxx.app;
URI uri;
if (args.length > 1 && args[1] != Context.getUndefinedValue()) {
scope = (Scriptable) args[1];
}
try {
uri = new URI(Context.toString(args[0]));
}
catch (URISyntaxException ex) {
throw Context.reportRuntimeError("Invalid argument: " + args[0]);
}
Application.ESXXScript es = app.resolveScript(cx, uri, true);
es.exec(cx, scope);
}
public static boolean jsFunction_checkTimeout(Context cx, Scriptable thisObj,
Object[] args, Function funcObj) {
if (Thread.currentThread().isInterrupted()) {
ESXX.checkTimeout(cx);
}
return true;
}
public static Scriptable jsFunction_parallel(final Context cx, Scriptable thisObj,
Object[] args, Function funcObj) {
JSESXX js_esxx = (JSESXX) thisObj;
Scriptable scope = funcObj.getParentScope();
Object[] fargs = Context.emptyArgs;
int timeout_ms = Integer.MAX_VALUE;
int max_tasks = Integer.MAX_VALUE;
// The first argument is the workload array
if (args.length < 1 || !(args[0] instanceof NativeArray)) {
throw Context.reportRuntimeError("First argument must be an Array");
}
final Object tasks[] = cx.getElements((NativeArray) args[0]);
// The second (optional) argument is the function arguments
if (args.length > 1 && args[1] != Context.getUndefinedValue()) {
fargs = cx.getElements((Scriptable) args[1]);
}
// The third (optional) argument is the timeout in s
if (args.length > 2 && args[2] != Context.getUndefinedValue()) {
timeout_ms = (int) (1000 * Context.toNumber(args[2]));
}
// The fourth (optional) argument is the parallel limit
if (args.length > 3 && args[3] != Context.getUndefinedValue()) {
max_tasks = (int) Context.toNumber(args[3]);
}
if (max_tasks <= 0) {
throw Context.reportRuntimeError("Workload limit must be greater than 0.");
}
ESXX.Workload[] workloads = new ESXX.Workload[tasks.length];
final Object[] final_fargs = fargs;
js_esxx.fork(cx, workloads, new ForkedFunction() {
public Object call(Context cx, int idx) {
if (tasks[idx] instanceof Function) {
Function func = (Function) tasks[idx];
Scriptable thiz = func.getParentScope();
return func.call(cx, thiz, thiz, final_fargs);
}
else {
return Context.getUndefinedValue();
}
}
}, timeout_ms, max_tasks);
return join(cx, scope, workloads);
}
public static Scriptable jsFunction_map(final Context cx, Scriptable thisObj,
final Object[] args, Function funcObj) {
JSESXX js_esxx = (JSESXX) thisObj;
// The first argument is the data array
if (args.length < 1 || !(args[0] instanceof NativeArray)) {
throw Context.reportRuntimeError("First argument must be an Array");
}
// The second argument is the function
if (args.length < 2 || !(args[1] instanceof Function)) {
throw Context.reportRuntimeError("Second argument must be a Function");
}
final Object data[] = cx.getElements((NativeArray) args[0]);
final Function func = (Function) args[1];
final Scriptable thiz = func.getParentScope();
int timeout_ms = Integer.MAX_VALUE;
int max_tasks = Integer.MAX_VALUE;
// The third (optional) argument is the timeout in s
if (args.length > 2 && args[2] != Context.getUndefinedValue()) {
timeout_ms = (int) (1000 * Context.toNumber(args[2]));
}
// The fourth (optional) argument is the parallel limit
if (args.length > 3 && args[3] != Context.getUndefinedValue()) {
max_tasks = (int) Context.toNumber(args[3]);
}
if (max_tasks <= 0) {
throw Context.reportRuntimeError("Workload limit must be greater than 0.");
}
ESXX.Workload[] workloads = new ESXX.Workload[data.length];
final Object undefined = Context.getUndefinedValue();
js_esxx.fork(cx, workloads, new ForkedFunction() {
public Object call(Context cx, int idx) {
if (data[idx] != undefined) {
Object fargs[] = { data[idx], idx, args[0] };
return func.call(cx, thiz, thiz, fargs);
}
else {
return undefined;
}
}
}, timeout_ms, max_tasks);
return join(cx, thisObj, workloads);
}
public JSLogger jsGet_log() {
return app.getJSAppLogger(Context.getCurrentContext());
}
public Object jsGet_host() {
return ESXX.getInstance().getHostObject();
}
public Scriptable jsGet_global() {
return getTopLevelScope(this);
}
public JSLRUCache jsGet_pls() {
return app.getPLS(Context.getCurrentContext());
}
public JSLRUCache jsGet_tls() {
return app.getTLS(Context.getCurrentContext());
}
public Scriptable jsGet_document() {
return app.getMainDocument();
}
public void jsSet_document(Scriptable doc) {
app.setMainDocument(doc);
}
public JSURI jsGet_uri() {
return app.getMainURI();
}
public Scriptable jsGet_paths() {
return app.getIncludePath();
}
public void jsSet_paths(Scriptable paths) {
app.setIncludePath(paths);
}
public JSURI jsGet_wd() {
return app.getJSWorkingDirectory(Context.getCurrentContext());
}
public void jsSet_wd(JSURI wd) {
app.setWorkingDirectory(wd.jsGet_javaURI());
}
public JSURI jsGet_location() {
return app.getJSCurrentLocation(Context.getCurrentContext());
}
private interface ForkedFunction {
public Object call(Context cx, int idx);
}
private void fork(Context cx,
ESXX.Workload[] workloads,
final ForkedFunction ff,
int timeout_ms, int max_tasks) {
ESXX esxx = ESXX.getInstance();
// Submit workloads, limit if asked to
final Semaphore limit = new Semaphore(max_tasks, false);
final AtomicBoolean abort = new AtomicBoolean(false);
for (int i = 0; i < workloads.length && !abort.get(); ++i) {
try {
limit.acquire();
}
catch (InterruptedException ex) {
ESXX.checkTimeout(cx);
break;
}
final int idx = i;
workloads[i] = esxx.addContextAction(cx, new ContextAction() {
public Object run(Context cx) {
boolean fine = false;
try {
Object res = ff.call(cx, idx);
fine = true;
return res;
}
finally {
if (!fine) {
abort.set(true);
}
limit.release();
}
}
}, app + "/" + Thread.currentThread() + " fork " + i, timeout_ms);
}
}
private static Scriptable join(Context cx, Scriptable scope,
ESXX.Workload[] workloads) {
Object undefined = Context.getUndefinedValue();
Object[] result = new Object[workloads.length];
Object[] errors = new Object[workloads.length];
boolean failed = false;
for (int i = 0; i < workloads.length; ++i) {
if (workloads[i] != null) {
try {
result[i] = workloads[i].getResult();
}
catch (ExecutionException ex) {
result[i] = undefined;
errors[i] = ex.getCause();
failed = true;
if (!(errors[i] instanceof RhinoException)) {
ex.printStackTrace();
}
}
catch (CancellationException ex) {
result[i] = undefined;
errors[i] = new WrappedException(new ESXXException.TimeOut());
failed = true;
}
catch (InterruptedException ex) {
ESXX.checkTimeout(cx);
}
}
else {
result[i] = undefined;
}
}
if (failed) {
throw new JavaScriptException(cx.newArray(scope, errors), null, 0);
}
return cx.newArray(scope, result);
}
private Application app;
}