
rapture.kernel.ScriptApiImpl Maven / Gradle / Ivy
/**
* The MIT License (MIT)
*
* Copyright (c) 2011-2016 Incapture Technologies LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package rapture.kernel;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import rapture.common.CallingContext;
import rapture.common.EntitlementSet;
import rapture.common.Messages;
import rapture.common.REPLVariable;
import rapture.common.RaptureFolderInfo;
import rapture.common.RaptureParameter;
import rapture.common.RaptureParameterType;
import rapture.common.RaptureScript;
import rapture.common.RaptureScriptLanguage;
import rapture.common.RaptureScriptPurpose;
import rapture.common.RaptureScriptStorage;
import rapture.common.RaptureSnippet;
import rapture.common.RaptureSnippetStorage;
import rapture.common.RaptureURI;
import rapture.common.ReflexREPLSession;
import rapture.common.ReflexREPLSessionStorage;
import rapture.common.Scheme;
import rapture.common.ScriptInterface;
import rapture.common.ScriptParameter;
import rapture.common.ScriptResult;
import rapture.common.api.ScriptApi;
import rapture.common.api.ScriptingApi;
import rapture.common.exception.RaptureException;
import rapture.common.exception.RaptureExceptionFactory;
import rapture.common.shared.script.DeleteScriptPayload;
import rapture.common.shared.script.GetScriptPayload;
import rapture.kernel.context.ContextValidator;
import rapture.script.IActivityInfo;
import rapture.script.IRaptureScript;
import rapture.script.ScriptFactory;
import rapture.script.reflex.ReflexHandler;
import rapture.script.reflex.ReflexRaptureScript;
import rapture.util.IDGenerator;
import reflex.IReflexHandler;
import reflex.IReflexOutputHandler;
import reflex.MetaParam;
import reflex.MetaReturn;
import reflex.ReflexExecutor;
import reflex.ReflexParser;
import reflex.ReflexTreeWalker;
import reflex.Scope;
import reflex.debug.NullDebugger;
import reflex.node.ReflexNode;
import reflex.util.ValueSerializer;
import reflex.value.ReflexValue;
import com.google.common.collect.Lists;
public class ScriptApiImpl extends KernelBase implements ScriptApi {
private static final Logger log = Logger.getLogger(ScriptApiImpl.class);
public ScriptApiImpl(Kernel raptureKernel) {
super(raptureKernel);
}
@Override
public RaptureScript createScript(CallingContext context, String scriptURI, RaptureScriptLanguage language, RaptureScriptPurpose purpose, String script) {
if (doesScriptExist(context, scriptURI)) {
throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST, String.format("Script %s already exists", scriptURI));
} else {
RaptureURI internalURI = new RaptureURI(scriptURI, Scheme.SCRIPT);
RaptureScript s = new RaptureScript();
s.setLanguage(language);
s.setPurpose(purpose);
s.setName(internalURI.getDocPath());
s.setScript(script);
s.setAuthority(internalURI.getAuthority());
RaptureScriptStorage.add(s, context.getUser(), Messages.getString("Script.createdScript")); //$NON-NLS-1$
return s;
}
}
@Override
public void createScriptLink(CallingContext context, String fromScriptURI, String toScriptURI) {
if (doesScriptExist(context, fromScriptURI)) {
throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST, String.format("Script %s already exists", fromScriptURI));
} else {
RaptureURI internalURI = new RaptureURI(fromScriptURI, Scheme.SCRIPT);
RaptureScript s = new RaptureScript();
s.setLanguage(RaptureScriptLanguage.REFLEX);
s.setPurpose(RaptureScriptPurpose.LINK);
s.setName(internalURI.getDocPath());
s.setScript(toScriptURI);
s.setAuthority(internalURI.getAuthority());
RaptureScriptStorage.add(s, context.getUser(), Messages.getString("Script.createdScript")); //$NON-NLS-1$
}
}
@Override
public void removeScriptLink(CallingContext context, String fromScriptURI) {
RaptureScript script = getScriptNoFollowLink(fromScriptURI);
if (script.getPurpose() == RaptureScriptPurpose.LINK) {
deleteScript(context, fromScriptURI);
} else {
throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST, String.format("Script %s is not a link", fromScriptURI));
}
}
@Override
public void deleteScript(CallingContext context, String scriptURI) {
if (scriptURI.endsWith("/")) {
RaptureScriptStorage.removeFolder(scriptURI);
} else {
RaptureURI internalURI = new RaptureURI(scriptURI, Scheme.SCRIPT);
RaptureScriptStorage.deleteByAddress(internalURI, context.getUser(), Messages.getString("Script.removedScript"));
}
}
@Override
public Boolean doesScriptExist(CallingContext context, String scriptURI) {
return getScriptNoFollowLink(scriptURI) != null;
}
private RaptureScript getScriptNoFollowLink(String scriptURI) {
RaptureURI internalURI = new RaptureURI(scriptURI, Scheme.SCRIPT);
return RaptureScriptStorage.readByAddress(internalURI);
}
@Override
public RaptureScript getScript(CallingContext context, String scriptURI) {
RaptureScript script = getScriptNoFollowLink(scriptURI);
if (script != null && script.getPurpose() == RaptureScriptPurpose.LINK) {
String targetUri = script.getScript().trim();
script = getScript(context, targetUri);
if (script != null) {
script.setScript(String.format("// LINKED TO %s\n%s", targetUri, script.getScript()));
}
}
return script;
}
@Override
public List getScriptNames(CallingContext context, String scriptURI) {
RaptureURI internalURI = new RaptureURI(scriptURI, Scheme.SCRIPT);
final List ret = new ArrayList();
List scripts = RaptureScriptStorage.readAll(internalURI.getAuthority());
for (RaptureScript script : scripts) {
ret.add(script.getName());
}
return ret;
}
@Override
public RaptureScript putScript(CallingContext context, String scriptURI, RaptureScript script) {
RaptureURI internalURI = new RaptureURI(scriptURI, Scheme.SCRIPT);
if (internalURI.hasDocPath() && !internalURI.getDocPath().equals(script.getName()))
throw RaptureExceptionFactory.create(
HttpURLConnection.HTTP_BAD_REQUEST,
String.format("Supplied URI " + scriptURI + " has a docPath which does not match the script name " + script.getName()));
script.setAuthority(internalURI.getAuthority());
RaptureScript currentlyThere = RaptureScriptStorage.readByAddress(script.getAddressURI());
if (currentlyThere != null && currentlyThere.getPurpose() == RaptureScriptPurpose.LINK) {
throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
String.format("You cannot write directly to a link, use %s instead", currentlyThere.getScript()));
}
RaptureScriptStorage.add(script, context.getUser(), Messages.getString("Script.updated")); //$NON-NLS-1$
return script;
}
@Override
public String runScript(CallingContext context, final String scriptURI, Map params) {
return runScriptStrategy(context, scriptURI, params, "");
}
@Override
public ScriptResult runScriptExtended(CallingContext context, final String scriptURI, Map params) {
return runScriptStrategy(context, scriptURI, params, new ScriptResult());
}
@SuppressWarnings("unchecked")
private T runScriptStrategy(CallingContext context, final String scriptURI, Map params, T retVal) {
RaptureScript script = getScript(context, scriptURI);
if (script == null) {
throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST, String.format("Script %s does not exists in repository", scriptURI));
}
final RaptureURI internalURI = new RaptureURI(scriptURI, Scheme.SCRIPT);
Kernel.getStackContainer().pushStack(context, internalURI.toString());
IRaptureScript scriptContext = ScriptFactory.getScript(script);
Map extraVals = new HashMap();
if (params != null) {
for (Map.Entry entry : params.entrySet()) {
extraVals.put(entry.getKey(), entry.getValue());
}
}
Kernel.writeComment(String.format(Messages.getString("Script.running"), scriptURI)); //$NON-NLS-1$
final String activityId = Kernel.getActivity()
.createActivity(ContextFactory.getKernelUser(), internalURI.toString(), "Running script", 1L, 100L);
IActivityInfo activityInfo = new IActivityInfo() {
@Override
public String getActivityId() {
return activityId;
}
@Override
public String getOtherId() {
return internalURI.toString();
}
};
if (retVal instanceof String) {
retVal = (T) scriptContext.runProgram(context, activityInfo, script, extraVals);
} else if (retVal instanceof ScriptResult) {
retVal = (T) scriptContext.runProgramExtended(context, activityInfo, script, extraVals);
}
Kernel.getActivity().finishActivity(ContextFactory.getKernelUser(), activityId, "Ran script");
Kernel.getKernel().getStat().registerRunScript();
Kernel.getStackContainer().popStack(context);
return retVal;
}
@Override
public String checkScript(CallingContext context, String scriptURI) {
RaptureScript script = getScript(context, scriptURI);
IRaptureScript scriptContext = ScriptFactory.getScript(script);
return scriptContext.validateProgram(context, script);
}
@Override
public Map listScriptsByUriPrefix(CallingContext context, String uriPrefix, int depth) {
RaptureURI internalUri = new RaptureURI(uriPrefix, Scheme.SCRIPT);
Boolean getAll = false;
String authority = internalUri.getAuthority();
Map ret = new HashMap();
// Schema level is special case.
if (authority.isEmpty()) {
--depth;
try {
List children = RaptureScriptStorage.getChildren("");
for (RaptureFolderInfo child : children) {
if (child.getName().isEmpty()) continue;
String uri = "script://" + child.getName();
ret.put(uri, child);
if (depth != 0) {
ret.putAll(listScriptsByUriPrefix(context, uri, depth));
}
}
} catch (RaptureException e) {
// permission denied
log.debug("No read permission for " + uriPrefix);
}
return ret;
}
String parentDocPath = internalUri.getShortPath();
if (parentDocPath.endsWith("/")) parentDocPath = parentDocPath.substring(0, parentDocPath.length() - 1);
if (log.isDebugEnabled()) {
log.debug("Loading all children from repo " + internalUri.getAuthority() + " with " + parentDocPath);
}
if (depth <= 0) getAll = true;
Stack parentsStack = new Stack();
parentsStack.push(parentDocPath);
int startDepth = StringUtils.countMatches(parentDocPath, "/");
while (!parentsStack.isEmpty()) {
String currParentDocPath = parentsStack.pop();
int currDepth = StringUtils.countMatches(currParentDocPath, "/") - startDepth;
if (!getAll && currDepth >= depth) continue;
// Make sure that you have permission to read the folder.
try {
GetScriptPayload requestObj = new GetScriptPayload();
requestObj.setContext(context);
// Note: Inconsistent
requestObj.setScriptURI(currParentDocPath);
ContextValidator.validateContext(context, EntitlementSet.Script_listScriptsByUriPrefix, requestObj);
boolean top = currParentDocPath.isEmpty();
List children = RaptureScriptStorage.getChildren(currParentDocPath);
if (children != null) {
for (RaptureFolderInfo child : children) {
String childDocPath = currParentDocPath + (top ? "" : "/") + child.getName();
if (child.getName().isEmpty()) continue;
// Special case: for Scripts childDocPath includes the authority
String childUri = Scheme.SCRIPT + "://" + childDocPath + (child.isFolder() ? "/" : "");
ret.put(childUri, child);
if (child.isFolder()) {
parentsStack.push(childDocPath);
}
}
}
} catch (RaptureException e) {
// permission denied
log.debug("No read permission on folder " + currParentDocPath);
}
}
return ret;
}
@Override
public List deleteScriptsByUriPrefix(CallingContext context, String uriPrefix) {
Map docs = listScriptsByUriPrefix(context, uriPrefix, Integer.MAX_VALUE);
List folders = new ArrayList<>();
Set notEmpty = new HashSet<>();
List removed = new ArrayList<>();
DeleteScriptPayload requestObj = new DeleteScriptPayload();
requestObj.setContext(context);
folders.add(uriPrefix.endsWith("/") ? uriPrefix : uriPrefix + "/");
for (Entry entry : docs.entrySet()) {
String uri = entry.getKey();
boolean isFolder = entry.getValue().isFolder();
try {
requestObj.setScriptUri(uri);
if (isFolder) {
ContextValidator.validateContext(context, EntitlementSet.Script_deleteScriptsByUriPrefix, requestObj);
folders.add(0, uri.substring(0, uri.length()-1));
} else {
ContextValidator.validateContext(context, EntitlementSet.Script_deleteScript, requestObj);
deleteScript(context, uri);
removed.add(uri);
}
} catch (RaptureException e) {
// permission denied
log.debug("Unable to delete "+uri+" : " + e.getMessage());
int colon = uri.indexOf(":") +3;
while (true) {
int slash = uri.lastIndexOf('/');
if (slash < colon) break;
uri = uri.substring(0, slash);
notEmpty.add(uri);
}
}
}
for (String uri : folders) {
if (notEmpty.contains(uri)) continue;
deleteScript(context, uri);
}
return removed;
}
/**
* These all manipulate a ReflexREPLSession object
*/
@Override
public String createREPLSession(CallingContext context) {
ReflexREPLSession session = new ReflexREPLSession();
session.setId(IDGenerator.getUUID());
session.setLastSeen(new Date());
session.setVars(new ArrayList());
ReflexREPLSessionStorage.add(session, context.getUser(), "Created session");
return session.getId();
}
@Override
public void destroyREPLSession(CallingContext context, String sessionId) {
ReflexREPLSessionStorage.deleteByFields(sessionId, context.getUser(), "Removed session");
}
@Override
public String evaluateREPL(CallingContext context, String sessionId, String line) {
// Now for the fun bit...
ReflexREPLSession session = ReflexREPLSessionStorage.readByFields(sessionId);
if (session == null) {
throw RaptureExceptionFactory.create("Unknown REPL session");
}
// Defreeze the scope, run it against the formatter
// So we really need to see whether we are still adding to the current
// line, and just add to that
// and not parse if so.
// Then, if the line we are executing contains a leading "def" it is the
// definition of a function
// and we should put everything from that to "the closing end or }" into
// a special area of the session
// that we can use to prefix the line (so that the functions are defined
// for each invocation)
String realLine;
if (line.endsWith("\\")) {
// Add to current line (minus the \\)
// and return
String newPartial = session.getPartialLine() + "\n" + line.substring(0, line.length() - 2);
session.setPartialLine(newPartial);
session.setLastSeen(new Date());
ReflexREPLSessionStorage.add(session, context.getUser(), "Updated session");
return "";
} else {
// See if we have a current line in the session, append this line to
// that and then clear the
// session current line - this is our initial new line.
realLine = session.getPartialLine() == null || session.getPartialLine().isEmpty() ? line : session.getPartialLine() + " " + line;
session.setPartialLine("");
// Does this line contain a def at the start? If it is we make this
// a functional declaration
if (realLine.trim().startsWith("def")) {
if (session.getFunctionDecls() == null) {
session.setFunctionDecls(new ArrayList());
}
session.getFunctionDecls().add(realLine);
session.setLastSeen(new Date());
ReflexREPLSessionStorage.add(session, context.getUser(), "Updated session");
return "";
}
}
// If we are here, realLine is what was entered, we need to prefix it
// with the function decls
if (session.getFunctionDecls() != null) {
StringBuilder realLineBuilder = new StringBuilder();
for (String v : session.getFunctionDecls()) {
realLineBuilder.append(v);
realLineBuilder.append("\n");
}
realLineBuilder.append(realLine);
realLine = realLineBuilder.toString();
}
// Now parse this line *here* for def/end pairs or def name { } - these
// are function declarations and
// we need to extract these out of the current line and put them
// elsewhere (in the session)
// Then we reform the line as being - (1) function declarations from
// session, + this line
// and that is what we parse and evaluate.
try {
IReflexHandler handler = new ReflexHandler(context);
ReflexTreeWalker walker = ReflexExecutor.getWalkerForProgram(realLine, handler);
walker.setReflexHandler(handler);
assignSessionToScope(session, walker.currentScope);
final StringBuilder sb = new StringBuilder();
walker.getReflexHandler().setOutputHandler(new IReflexOutputHandler() {
@Override
public boolean hasCapability() {
return true;
}
@Override
public void printLog(String text) {
sb.append(text);
sb.append("\n");
}
@Override
public void printOutput(String text) {
sb.append(text);
}
@Override
public void setApi(ScriptingApi api) {
}
});
ReflexNode res = walker.walk();
res.evaluate(new NullDebugger(), walker.currentScope);
session.setVars(getVarsFromScope(walker.currentScope));
session.setLastSeen(new Date());
ReflexREPLSessionStorage.add(session, context.getUser(), "Updated session");
return sb.toString();
} catch (Exception e) {
// Don't throw this as the far end cannot handle it
return "Unable to execute line " + e.toString();
}
}
private List getVarsFromScope(Scope s) {
List ret = new ArrayList();
for (Map.Entry entry : s.retrieveVariables().entrySet()) {
REPLVariable repl = new REPLVariable();
repl.setName(entry.getKey());
ReflexValue v = entry.getValue();
repl.setSerializedVar(ValueSerializer.serialize(v));
ret.add(repl);
}
return ret;
}
private void assignSessionToScope(ReflexREPLSession session, Scope s) {
try {
for (REPLVariable var : session.getVars()) {
s.assign(var.getName(), ValueSerializer.deserialize(var.getSerializedVar()));
}
} catch (ClassNotFoundException e) {
throw RaptureExceptionFactory.create("Could not determine scope variables");
}
}
@Override
public void archiveOldREPLSessions(CallingContext context, Long ageInMinutes) {
List sessions = ReflexREPLSessionStorage.getChildren("");
for (RaptureFolderInfo sess : sessions) {
String sessionId = sess.getName();
// For now, ignore the age/date issue as the session is still in
// flux and we may have some
// old junk in there which won't be convertable from the JSON
// storage
log.info("Removing " + sessionId);
ReflexREPLSessionStorage.deleteByFields(sessionId, context.getUser(), "Removed due to age");
}
}
@Override
public RaptureSnippet createSnippet(CallingContext context, String snippetURI, String snippet) {
RaptureSnippet rsnippet = new RaptureSnippet();
RaptureURI internalURI = new RaptureURI(snippetURI, Scheme.SNIPPET);
rsnippet.setName(internalURI.getDocPath());
rsnippet.setSnippet(snippet);
rsnippet.setAuthority(internalURI.getAuthority());
RaptureSnippetStorage.add(rsnippet, context.getUser(), "Added snippet");
return rsnippet;
}
@Override
public List getSnippetChildren(CallingContext context, String prefix) {
return RaptureSnippetStorage.getChildren(prefix);
}
@Override
public void deleteSnippet(CallingContext context, String snippetURI) {
RaptureURI internalURI = new RaptureURI(snippetURI, Scheme.SNIPPET);
RaptureSnippetStorage.deleteByAddress(internalURI, context.getUser(), "Removed snippet");
}
@Override
public RaptureSnippet getSnippet(CallingContext context, String snippetURI) {
RaptureURI internalURI = new RaptureURI(snippetURI, Scheme.SNIPPET);
return RaptureSnippetStorage.readByAddress(internalURI);
}
@Override
public RaptureScript putRawScript(CallingContext context, String scriptURI, String content, String language, String purpose, List param_types,
List param_names) {
RaptureURI uri = new RaptureURI(scriptURI, Scheme.SCRIPT);
RaptureScript script = new RaptureScript();
RaptureScriptLanguage lang;
RaptureScriptPurpose purp;
try {
lang = RaptureScriptLanguage.valueOf(language);
} catch (Exception e) {
throw RaptureExceptionFactory.create("Unknown script language: [" + language + "]");
}
try {
purp = RaptureScriptPurpose.valueOf(purpose);
} catch (Exception e) {
throw RaptureExceptionFactory.create("Unknown script purpose: [" + purpose + "]");
}
if (param_names.size() != param_types.size()) {
throw RaptureExceptionFactory.create("Paramter type list must be the same length as parameter name list");
}
List parms = Lists.newArrayList();
Iterator name_iter = param_names.iterator();
Iterator type_iter = param_types.iterator();
while (name_iter.hasNext()) {
parms.add(makeFormalParam(type_iter.next(), name_iter.next()));
}
script.setAuthority(uri.getAuthority());
script.setName(uri.getDocPath());
script.setLanguage(lang);
script.setPurpose(purp);
script.setScript(content);
script.setParameters(parms);
RaptureScriptStorage.add(script, context.getUser(), "Update");
return script;
}
private RaptureParameter makeFormalParam(String type, String name) {
RaptureParameter result = new RaptureParameter();
result.setName(name);
RaptureParameterType rpt;
try {
rpt = RaptureParameterType.valueOf(type);
} catch (Exception e) {
throw RaptureExceptionFactory.create("Unknown type :" + type);
}
result.setParameterType(rpt);
return result;
}
@Override
public ScriptInterface getInterface(CallingContext context, String scriptURI) {
ScriptInterface result = new ScriptInterface();
HashMap inputs = new HashMap<>();
ScriptParameter output = new ScriptParameter();
RaptureScript script = getScriptNoFollowLink(scriptURI);
if (script == null) {
log.error("ScriptApiImpl.getInterface, script not found returning NULL, scriptURI: " + scriptURI);
return result;
}
ReflexRaptureScript rrs = new ReflexRaptureScript();
try {
if (script.getScript() == null) {
log.error("ScriptApiImpl.getInterface, script.getScript() returning NULL, scriptURI: " + scriptURI);
return result;
}
ReflexParser parser = rrs.getParser(ContextFactory.getKernelUser(), script.getScript());
parser.parse();
if (parser.scriptInfo == null) {
log.error("ScriptApiImpl.getInterface, parser.scriptInfo NULL, scriptURI: " + scriptURI);
return result;
}
List parameters = parser.scriptInfo.getParameters();
for (MetaParam param : parameters) {
ScriptParameter scriptParam = new ScriptParameter();
scriptParam.setDescription(param.getDescription());
String type = param.getParameterType().toUpperCase();
scriptParam.setParameterType(RaptureParameterType.valueOf(type));
inputs.put(param.getParameterName(), scriptParam);
}
MetaReturn returnInfo = parser.scriptInfo.getReturnInfo();
String type = returnInfo.getType().toUpperCase();
output.setParameterType(RaptureParameterType.valueOf(type));
output.setDescription(returnInfo.getDescription());
result.setProperties(parser.scriptInfo.getProperties());
} catch (Exception e) {
log.error("ScriptApiImpl.getInterface, scriptURI: " + scriptURI);
log.log(Level.ERROR, "ScriptApiImpl.getInterface, exception" + e.getMessage(), e);
throw RaptureExceptionFactory.create("Exception while parsing script parameters: " + e);
}
result.setInputs(inputs);
result.setRet(output);
return result;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy