/*
* Copyright 2012 Jason Miller
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jj.script;
import static jj.server.ServerLocation.Virtual;
import java.nio.file.Paths;
import javax.inject.Inject;
import javax.inject.Singleton;
import jj.resource.ResourceFinder;
import jj.script.module.ModuleScriptEnvironment;
import jj.script.module.RequiredModule;
import jj.script.module.RootScriptEnvironment;
import org.mozilla.javascript.BaseFunction;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
/**
* @author jason
*
*/
@Singleton
class RequireInnerFunction extends BaseFunction {
private static final long serialVersionUID = -3809338081179905958L;
private final CurrentScriptEnvironment env;
private final ResourceFinder resourceFinder;
@Inject
RequireInnerFunction(
final CurrentScriptEnvironment env,
final ResourceFinder resourceFinder
) {
this.env = env;
this.resourceFinder = resourceFinder;
}
private String toModuleIdentifier(final String input, final String parent) {
// if absolute, from the root
if (input.startsWith("/")) {
return input.substring(1);
} else if (input.startsWith(ModuleScriptEnvironment.API_PREFIX)) {
return input;
}
return Paths.get(parent).resolveSibling(input).normalize().toString();
}
@Override
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
String moduleIdentifier = toModuleIdentifier(String.valueOf(args[0]), String.valueOf(args[1]));
RootScriptEnvironment<?> parent = env.currentRootScriptEnvironment();
RequiredModule requiredModule = new RequiredModule(parent, moduleIdentifier);
ModuleScriptEnvironment scriptEnvironment =
resourceFinder.findResource(
ModuleScriptEnvironment.class,
Virtual,
moduleIdentifier,
requiredModule
);
// if we find a ModuleScriptEnvironment, return the exports, even
// if it is still being initialized. this will happen in circular
// dependency scenarios. MAKE IT CLEAR IN THE DOCS that this can
// happen. node does the same thing so it's fair
// on the continuation side, the system does wait for initialization to
// finish before restarting the waiting task, because if multiple requests
// for the same non-existent module result in continuations, then it's
// probably a lot of requests hitting the same uninitialized script at
// once
// it may be possible to determine the circular invocation and break it by
// returning only in that case, thereby allowing most required modules
// to be complete on parent resumption, but i'm not yet sure what that would
// take
// not sure what can guard against a cycle in this environment, though.
// would involve that execution trace concept for sure
// if there is no ModuleScriptEnvironment found, then we need a
// continuation to start processing it properly
// this is because if we don't do this as its own top call, then any
// continuation inside the module will fail because there will be the
// pending top call caused by this function. continuations are like
// violence, any problem can be solved by using MOAR!
if (scriptEnvironment == null) {
throw env.preparedContinuation(requiredModule);
}
return scriptEnvironment.exports();
}
@Override
public Scriptable construct(Context cx, Scriptable scope, Object[] args) {
throw new AssertionError("do not attempt to construct this function");
}
}