diff --git a/src/main/java/caosdb/server/CaosDBException.java b/src/main/java/caosdb/server/CaosDBException.java index adfca3d06f83d990ac540318138093cd0485cf7b..3e0e50769cd3f08423ea83fa851b2cdfca54e50f 100644 --- a/src/main/java/caosdb/server/CaosDBException.java +++ b/src/main/java/caosdb/server/CaosDBException.java @@ -22,8 +22,8 @@ */ package caosdb.server; -public class CaosDBException extends Exception { - /** */ +public class CaosDBException extends RuntimeException { + private static final long serialVersionUID = 5317733089121727021L; public CaosDBException(final String string) { diff --git a/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java b/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java index 2f4cf5f92d5307f37e5838957f20f7157fdfe7c1..e390d3c55576e774a78e89f17de73ed950b78cf4 100644 --- a/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java +++ b/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java @@ -256,6 +256,10 @@ public abstract class AbstractCaosDBServerResource extends ServerResource { this.xslScript = s; } + protected JdomRepresentation ok(Element root) { + return ok(new Document(root)); + } + protected JdomRepresentation ok(final Document doc) { return new JdomRepresentation(doc, MediaType.TEXT_XML, " ", getReference(), getXSLScript()); } @@ -339,6 +343,14 @@ public abstract class AbstractCaosDBServerResource extends ServerResource { return this.flags; } + protected Element generateRootElement(Element... elements) { + Element root = generateRootElement(); + for (Element e : elements) { + root.addContent(e); + } + return root; + } + public static class XMLParser { private final LinkedList<SAXBuilder> pool = new LinkedList<SAXBuilder>(); private final int max = 25; diff --git a/src/main/java/caosdb/server/resource/ScriptingResource.java b/src/main/java/caosdb/server/resource/ScriptingResource.java index fa67fac5597116c7eea58a48fdfbd267d20114b6..30a9cdb3fe9e818b0eb67111c0511000b83f3dea 100644 --- a/src/main/java/caosdb/server/resource/ScriptingResource.java +++ b/src/main/java/caosdb/server/resource/ScriptingResource.java @@ -22,28 +22,29 @@ */ package caosdb.server.resource; -import caosdb.server.CaosDBException; import caosdb.server.FileSystem; import caosdb.server.accessControl.Principal; import caosdb.server.accessControl.SessionToken; import caosdb.server.entity.FileProperties; import caosdb.server.entity.Message; -import caosdb.server.scripting.ScriptingUtils; +import caosdb.server.scripting.CallerSerializer; import caosdb.server.scripting.ServerSideScriptingCaller; +import caosdb.server.utils.Serializer; +import caosdb.server.utils.ServerMessages; import caosdb.server.utils.Utils; import com.ibm.icu.text.Collator; -import java.io.File; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; import java.util.List; +import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemStream; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.io.FileUtils; -import org.jdom2.Document; import org.jdom2.Element; import org.restlet.data.CharacterSet; import org.restlet.data.Form; @@ -57,94 +58,55 @@ import org.restlet.representation.Representation; public class ScriptingResource extends AbstractCaosDBServerResource { private ServerSideScriptingCaller caller; + private Collection<FileProperties> deleteFiles = new LinkedList<>(); @Override public Logger getLogger() { return Logger.getLogger(this.getClass().getName()); } - /* - * TODO move to pyinttest - */ - public void setupTestCase(String queryValue) throws IOException { - switch (queryValue) { - case "call_not_executable": - File file1 = new ScriptingUtils().getScriptFile("not_executable"); - file1.getParentFile().mkdirs(); - file1.createNewFile(); - break; - case "call_ok": - File file2 = new ScriptingUtils().getScriptFile("ok"); - file2.getParentFile().mkdirs(); - file2.createNewFile(); - FileUtils.write(file2, "echo ok"); - file2.setExecutable(true); - break; - case "call_err": - File file3 = new ScriptingUtils().getScriptFile("err"); - file3.getParentFile().mkdirs(); - file3.createNewFile(); - FileUtils.write(file3, ">&2 echo \"err\"; exit 1"); - file3.setExecutable(true); - break; - default: - break; - } - } + public Element generateRootElement(ServerSideScriptingCaller caller) { + Serializer<ServerSideScriptingCaller, Element> xmlSerializer = new CallerSerializer(); + Element callerElement = xmlSerializer.serialize(caller); - /* - * TODO move to serialization class - */ - public Element generateResponse( - Integer code, String[] commandLine, String stdOut, String stdErr) { - Element callElem = new Element("call"); - callElem.setText(String.join(" ", commandLine)); - - Element stdErrElem = new Element("stderr"); - stdErrElem.addContent(stdErr); - - Element stdOutElem = new Element("stdout"); - stdOutElem.addContent(stdOut); - - Element scriptElem = new Element("script"); - scriptElem.setAttribute("code", code.toString()); - scriptElem.addContent(callElem); - scriptElem.addContent(stdOutElem); - scriptElem.addContent(stdErrElem); - - Element root = generateRootElement(); - root.addContent(scriptElem); - return root; + return generateRootElement(callerElement); } @Override protected Representation httpPostInChildClass(Representation entity) throws Exception { MediaType mediaType = entity.getMediaType(); - int code; try { if (mediaType.equals(MediaType.MULTIPART_FORM_DATA, true)) { - code = handleMultiparts(entity); + handleMultiparts(entity); } else if (mediaType.equals(MediaType.APPLICATION_WWW_FORM)) { - code = handleForm(new Form(entity)); + handleForm(new Form(entity)); } else { getResponse().setStatus(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE); return null; } } catch (Message m) { return error(m, Status.valueOf(m.getCode())); + } finally { + deleteTmpFiles(); + } + return ok(generateRootElement(this.caller)); + } + + private void deleteTmpFiles() { + for (FileProperties p : deleteFiles) { + try { + p.deleteFile(); + } catch (Exception t) { + if (getLogger().isLoggable(Level.WARNING)) { + getLogger().warning("Could not delete tmp file: " + t.toString()); + } + } } - return ok( - new Document( - generateResponse( - code, - this.caller.getCommandLine(), - this.caller.getStdOut(), - this.caller.getStdErr()))); } public int handleMultiparts(Representation entity) - throws FileUploadException, IOException, NoSuchAlgorithmException, CaosDBException, Message { + throws FileUploadException, IOException, NoSuchAlgorithmException, Message { final DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setSizeThreshold(1000240); final RestletFileUpload upload = new RestletFileUpload(factory); @@ -158,11 +120,15 @@ public class ScriptingResource extends AbstractCaosDBServerResource { if (item.isFormField()) { // put plain text form field into the form CharacterSet characterSet = ContentType.readCharacterSet(item.getContentType()); - String value = Utils.InputStream2String(item.openStream(), (characterSet!=null?characterSet.toString():CharacterSet.UTF_8.toString())); + String value = + Utils.InputStream2String( + item.openStream(), + (characterSet != null ? characterSet.toString() : CharacterSet.UTF_8.toString())); form.add(new Parameter(item.getFieldName(), value)); } else { // -> this is a file, store in tmp dir final FileProperties file = FileSystem.upload(item, this.getSRID()); + deleteTmpFileAfterTermination(file); file.setPath(item.getName()); file.setTmpIdentifyer(item.getFieldName()); files.add(file); @@ -175,11 +141,15 @@ public class ScriptingResource extends AbstractCaosDBServerResource { return callScript(form, files); } + private void deleteTmpFileAfterTermination(FileProperties file) { + deleteFiles.add(file); + } + public int handleForm(Form form) throws Message { return callScript(form, null); } - public List<String> form2CommandLine(Form form) { + public List<String> form2CommandLine(Form form) throws Message { ArrayList<String> commandLine = new ArrayList<>(form.size()); ArrayList<Parameter> positionalArgs = new ArrayList<>(form.size() - 1); @@ -203,7 +173,7 @@ public class ScriptingResource extends AbstractCaosDBServerResource { if (commandLine.get(0).length() == 0) { // first item still empty - throw new RuntimeException("Missing form field `call`."); + throw ServerMessages.SERVER_SIDE_SCRIPT_MISSING_CALL; } return commandLine; } diff --git a/src/main/java/caosdb/server/scripting/CallerSerializer.java b/src/main/java/caosdb/server/scripting/CallerSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..1bd265650bed0d5de865cc91b29db3327b56174b --- /dev/null +++ b/src/main/java/caosdb/server/scripting/CallerSerializer.java @@ -0,0 +1,36 @@ +package caosdb.server.scripting; + +import caosdb.server.CaosDBException; +import caosdb.server.utils.Serializer; +import java.io.IOException; +import org.jdom2.Element; + +public class CallerSerializer implements Serializer<ServerSideScriptingCaller, Element> { + + @Override + public Element serialize(ServerSideScriptingCaller caller) { + Element command = new Element("call"); + command.setText(String.join(" ", caller.getCommandLine())); + + Element stdout = new Element("stdout"); + try { + stdout.addContent(caller.getStdOut()); + } catch (IOException e) { + throw new CaosDBException(e); + } + + Element stderr = new Element("stderr"); + try { + stderr.addContent(caller.getStdErr()); + } catch (IOException e) { + throw new CaosDBException(e); + } + + Element script = new Element("script"); + script.setAttribute("code", caller.getCode().toString()); + script.addContent(command); + script.addContent(stdout); + script.addContent(stderr); + return script; + } +} diff --git a/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java b/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java index 61e914ba0aca24d50412141ea23eb55fe673f6ff..2455916ec6245f1e721908b6e91f65b230b60a3a 100644 --- a/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java +++ b/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java @@ -37,6 +37,7 @@ import org.apache.commons.io.FileUtils; public class ServerSideScriptingCaller { public static final String UPLOAD_FILES_DIR = ".upload_files"; + public static final Integer STARTED = -1; private final String[] commandLine; private final int timeoutMs; private ScriptingUtils utils; @@ -47,6 +48,11 @@ public class ServerSideScriptingCaller { private File stdErrFile; private String stdErr = null; private String stdOut; + private Integer code = null; + + public Integer getCode() { + return code; + } File getUploadFilesDir() { return getTmpWorkingDir().toPath().resolve(UPLOAD_FILES_DIR).toFile(); @@ -188,9 +194,11 @@ public class ServerSideScriptingCaller { pb.redirectError(Redirect.to(getStdErrFile())); pb.directory(getTmpWorkingDir()); + code = STARTED; final TimeoutProcess process = new TimeoutProcess(pb.start(), getTimeoutMs()); - return process.waitFor(); + code = process.waitFor(); + return code; } public File getStdOutFile() { diff --git a/src/main/java/caosdb/server/utils/Serializer.java b/src/main/java/caosdb/server/utils/Serializer.java new file mode 100644 index 0000000000000000000000000000000000000000..5508e99388a9e4191fd524ccbfc3e30007f6759a --- /dev/null +++ b/src/main/java/caosdb/server/utils/Serializer.java @@ -0,0 +1,6 @@ +package caosdb.server.utils; + +public interface Serializer<T, S> { + + public S serialize(T object); +} diff --git a/src/test/java/caosdb/server/resource/TestScriptingResource.java b/src/test/java/caosdb/server/resource/TestScriptingResource.java index 713d30b981c5d0f6b9fc58ea550d084c192c0313..3b035fe703409605c59a77df70421e7516230376 100644 --- a/src/test/java/caosdb/server/resource/TestScriptingResource.java +++ b/src/test/java/caosdb/server/resource/TestScriptingResource.java @@ -71,7 +71,7 @@ public class TestScriptingResource { } @Test - public void testForm2invocation() { + public void testForm2invocation() throws Message { Form form = new Form( "-Ooption=OPTION&call=CA%20LL&-Ooption2=OPTION2&-p=POS1&-p4=POS3&-p2=POS2&IGNORED");