Skip to content
Snippets Groups Projects
Commit 7452307e authored by Timm Fitschen's avatar Timm Fitschen Committed by Quazgar
Browse files

versioned builds in webui

parent b46875db
Branches
Tags
No related merge requests found
Showing
with 475 additions and 650 deletions
......@@ -49,12 +49,11 @@ import caosdb.server.resource.ScriptingResource;
import caosdb.server.resource.ServerLogsResource;
import caosdb.server.resource.ServerPropertiesResource;
import caosdb.server.resource.SharedFileResource;
import caosdb.server.resource.TestCaseFileSystemResource;
import caosdb.server.resource.TestCaseResource;
import caosdb.server.resource.ThumbnailsResource;
import caosdb.server.resource.UserResource;
import caosdb.server.resource.UserRolesResource;
import caosdb.server.resource.Webinterface;
import caosdb.server.resource.WebinterfaceBuildNumber;
import caosdb.server.resource.transaction.EntityResource;
import caosdb.server.terminal.CaosDBTerminal;
import caosdb.server.terminal.StatsPanel;
......@@ -581,39 +580,8 @@ public class CaosDBServer extends Application {
}
};
// -- Section only for debug mode --
if (isDebugMode()) {
baseRouter.attach("/TestCase/", DefaultResource.class);
final Variable pathVariable =
baseRouter
.attach(
"/TestCase/FileSystem/{path}",
baseRouter.createFinder(TestCaseFileSystemResource.class))
.getTemplate()
.getDefaultVariable();
pathVariable.setRequired(false);
pathVariable.setType(Variable.TYPE_URI_PATH);
pathVariable.setDefaultValue("");
baseRouter
.attach("/TestCase/Thumbnails/{path}", ThumbnailsResource.class)
.getTemplate()
.getDefaultVariable()
.setType(Variable.TYPE_URI_PATH);
baseRouter
.attach("/TestCase/webinterface/{path}", Webinterface.class)
.getTemplate()
.getDefaultVariable()
.setType(Variable.TYPE_URI_PATH);
baseRouter.attach("/TestCase/Entity", TestCaseResource.class);
baseRouter.attach("/TestCase/Entity/", TestCaseResource.class);
baseRouter.attach("/TestCase/Entity/{specifier}", TestCaseResource.class);
}
// -- End of debug section --
// These routes can be used without logging in:
baseRouter.attach("/webinterface/version/build", WebinterfaceBuildNumber.class);
baseRouter
.attach("/webinterface/{path}", Webinterface.class)
.getTemplate()
......
......@@ -33,6 +33,7 @@ import caosdb.server.accessControl.UserSources;
import caosdb.server.database.backend.implementation.MySQL.ConnectionException;
import caosdb.server.entity.Message;
import caosdb.server.utils.ServerMessages;
import caosdb.server.utils.WebinterfaceUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
......@@ -55,7 +56,6 @@ import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.restlet.data.Form;
import org.restlet.data.Header;
import org.restlet.data.MediaType;
import org.restlet.data.Parameter;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
......@@ -81,7 +81,12 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
private String[] requestedItems = null;
private ArrayList<Integer> requestedIDs = new ArrayList<Integer>();
private ArrayList<String> requestedNames = new ArrayList<String>();
private String xslScript = "webcaosdb.xsl";
private WebinterfaceUtils utils;
/** Return the {@link WebinterfaceUtils} instance for this resource. */
public WebinterfaceUtils getUtils() {
return this.utils;
}
public static class xmlNotWellFormedException extends Exception {
private static final long serialVersionUID = -6836378704013776849L;
......@@ -116,6 +121,8 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
getRequest().setEntity(r);
}
this.utils = WebinterfaceUtils.getInstance(getHostRef());
this.timestamp = getRequest().getDate().getTime();
this.sRID = getRequest().getAttributes().get("SRID").toString();
......@@ -325,11 +332,7 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
}
protected String getXSLScript() {
return this.xslScript;
}
protected void setXSLScript(final String s) {
this.xslScript = s;
return this.utils.getWebinterfaceURI("webcaosdb.xsl");
}
/** Wrap an element for an "OK" response. */
......@@ -337,18 +340,28 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
return ok(new Document(root));
}
/**
* Return a JDomRepresentation of the document and leave the {@link Response}'s {@link Status}
* where it is - which is usually 200 - OK when this method happens to be called.
*/
protected JdomRepresentation ok(final Document doc) {
return new JdomRepresentation(doc, MediaType.TEXT_XML, " ", getReference(), getXSLScript());
return new JdomRepresentation(doc, " ", getXSLScript());
}
/**
* Return a Representation containing the error message and set the {@link Response}'s {@link
* Status}.
*
* @param m - the error message.
* @param status - the response status
* @return A Representation of the error.
*/
protected Representation error(final Message m, final Status status) {
final Document doc = new Document();
final Element root = generateRootElement();
root.addContent(m.toElement().setName("Error"));
doc.setRootElement(root);
return error(
new JdomRepresentation(doc, MediaType.TEXT_XML, " ", getReference(), getXSLScript()),
status);
return error(new JdomRepresentation(doc, " ", getXSLScript()), status);
}
protected Representation error(Representation entity, Status status) {
......@@ -364,12 +377,19 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
return error((Representation) null, status);
}
/**
* Return a Representation containing the warning message but leave the {@link Response}'s {@link
* Status} where it is - which is usually 200 - OK when this method happens to be called.
*
* @param m - the warning message.
* @return A Representation of the warning.
*/
protected JdomRepresentation warning(final Message m) {
final Document doc = new Document();
final Element root = generateRootElement();
root.addContent(m.toElement().setName("Warning"));
doc.setRootElement(root);
return new JdomRepresentation(doc, MediaType.TEXT_XML, " ", getReference(), getXSLScript());
return new JdomRepresentation(doc, " ", getXSLScript());
}
protected Representation noWellFormedNess() {
......
......@@ -56,30 +56,21 @@ public class JdomRepresentation extends WriterRepresentation {
private final Document document;
private final String indent;
private final Reference reference;
private final String xslPath;
/**
* Constructor.
*
* @param mediaType This should be TEXT_XML, but we'll leave it open to use others.
* @param document A JDOM Document parsed from elsewhere.
* @param indent To make that representation better to read by a human.
* @param reference
* @param indent Usually a number of whitespaces for better human readbility.
* @param xslPath A String containing a meaningful path to a xslt file.
*/
public JdomRepresentation(
final Document document,
final MediaType mediaType,
final String indent,
final Reference reference,
final String xslPath) {
super(mediaType);
this.xslPath = xslPath;
this.reference = reference;
public JdomRepresentation(final Document document, final String indent, final String xslPath) {
super(MediaType.TEXT_XML);
this.indent = indent;
this.document = document;
addStyleSheet();
if (xslPath != null && document != null) {
addStyleSheet(document, xslPath);
}
}
public static final String getWebUIRef(final Reference reference) {
......@@ -89,14 +80,10 @@ public class JdomRepresentation extends WriterRepresentation {
}
/** adds the xslt processing instruction to the document. */
protected final void addStyleSheet() {
if (this.document != null) {
protected final void addStyleSheet(Document document, String xslPath) {
final ProcessingInstruction pi =
new ProcessingInstruction(
"xml-stylesheet",
"type=\"text/xsl\" href=\"" + getWebUIRef(this.reference) + this.xslPath + "\" ");
this.document.getContent().add(0, pi);
}
new ProcessingInstruction("xml-stylesheet", "type=\"text/xsl\" href=\"" + xslPath + "\" ");
document.getContent().add(0, pi);
}
@Override
......
......@@ -214,6 +214,7 @@ public class ScriptingResource extends AbstractCaosDBServerResource {
public int callScript(
List<String> commandLine, Integer timeoutMs, List<FileProperties> files, Object authToken)
throws Message {
// TODO getUser().checkPermission("SCRIPTING:EXECUTE:" + commandLine.get(0));
caller =
new ServerSideScriptingCaller(
commandLine.toArray(new String[commandLine.size()]), timeoutMs, files, authToken);
......
......@@ -13,7 +13,11 @@ public class ServerPropertiesResource extends AbstractCaosDBServerResource {
@Override
protected void doInit() {
super.doInit();
setXSLScript("xsl/server_properties.xsl");
}
@Override
protected String getXSLScript() {
return getUtils().getWebinterfaceURI("xsl/server_properties.xsl");
}
@Override
......
/*
* ** header v3.0
* This file is a part of the CaosDB Project.
*
* Copyright (C) 2018 Research Group Biomedical Physics,
* Max-Planck-Institute for Dynamics and Self-Organization Göttingen
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ** end header
*/
package caosdb.server.resource;
import caosdb.server.accessControl.Principal;
import caosdb.server.entity.FileProperties;
import caosdb.server.utils.FileUtils;
import java.io.File;
import org.jdom2.Element;
public class TestCaseFileSystemResource extends FileSystemResource {
@Override
protected void doInit() {
super.doInit();
setXSLScript("webcaosdb.xsl");
}
private static final String BASEPATH = "./testfiles/";
@Override
protected File getFile(final String path) throws Exception {
final File f = new File(BASEPATH + path);
return (f != null && f.exists() ? f : null);
}
@Override
protected String getEntityID(final String path) throws Exception {
return "123412341234";
}
@Override
protected Element generateRootElement() {
final Element retRoot = new Element("Response");
if (getUser() != null && getUser().isAuthenticated()) {
retRoot.setAttribute("username", ((Principal) getUser().getPrincipal()).getUsername());
retRoot.setAttribute("realm", ((Principal) getUser().getPrincipal()).getRealm());
}
retRoot.setAttribute("srid", getSRID());
if (this.getCRID() != null) {
retRoot.setAttribute("crid", this.getCRID());
}
retRoot.setAttribute("timestamp", getTimestamp().toString());
retRoot.setAttribute("baseuri", getRootRef().toString() + "/TestCase/");
return retRoot;
}
public static FileProperties getFileProperties(final String path) {
final File f = new File(BASEPATH + path);
return new FileProperties(FileUtils.getChecksum(f), path, f.length());
}
}
/*
* ** header v3.0
* This file is a part of the CaosDB Project.
*
* Copyright (C) 2018 Research Group Biomedical Physics,
* Max-Planck-Institute for Dynamics and Self-Organization Göttingen
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ** end header
*/
package caosdb.server.resource;
import caosdb.datetime.DateTimeFactory2;
import caosdb.datetime.UTCDateTime;
import caosdb.server.CaosDBException;
import caosdb.server.database.backend.implementation.MySQL.ConnectionException;
import caosdb.server.datatype.AbstractCollectionDatatype;
import caosdb.server.datatype.AbstractDatatype;
import caosdb.server.datatype.BooleanDatatype;
import caosdb.server.datatype.BooleanValue;
import caosdb.server.datatype.CollectionValue;
import caosdb.server.datatype.DateTimeDatatype;
import caosdb.server.datatype.DoubleDatatype;
import caosdb.server.datatype.GenericValue;
import caosdb.server.datatype.IntegerDatatype;
import caosdb.server.datatype.ListDatatype;
import caosdb.server.datatype.ReferenceDatatype2;
import caosdb.server.datatype.ReferenceValue;
import caosdb.server.datatype.TextDatatype;
import caosdb.server.datatype.Value;
import caosdb.server.entity.Entity;
import caosdb.server.entity.EntityInterface;
import caosdb.server.entity.MagicTypes;
import caosdb.server.entity.Message;
import caosdb.server.entity.Message.MessageType;
import caosdb.server.entity.RetrieveEntity;
import caosdb.server.entity.Role;
import caosdb.server.entity.StatementStatus;
import caosdb.server.entity.container.PropertyContainer;
import caosdb.server.entity.wrapper.Parent;
import caosdb.server.entity.wrapper.Property;
import caosdb.server.entity.xml.ToElementable;
import caosdb.server.query.Query;
import caosdb.server.query.Query.ParsingException;
import caosdb.server.utils.ServerMessages;
import caosdb.server.utils.TransactionLogMessage;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import org.jdom2.Document;
import org.jdom2.Element;
import org.restlet.representation.Representation;
public class TestCaseResource extends AbstractCaosDBServerResource {
@Override
protected void doInit() {
super.doInit();
setXSLScript("webcaosdb.xsl");
}
@Override
protected Element generateRootElement() {
final Element retRoot = new Element("Response");
retRoot.setAttribute("username", "TestUser");
retRoot.setAttribute("realm", "PAM");
retRoot.setAttribute("srid", getSRID());
if (this.getCRID() != null) {
retRoot.setAttribute("crid", this.getCRID());
}
retRoot.setAttribute("timestamp", getTimestamp().toString());
retRoot.setAttribute("baseuri", getRootRef().toString() + "/TestCase/");
return retRoot;
}
@Override
protected synchronized Representation httpGetInChildClass()
throws ConnectionException, IOException, SQLException, CaosDBException,
NoSuchAlgorithmException, Exception {
final Element rootElem = generateRootElement();
final Document doc = new Document();
final Collection<ToElementable> selectedTestCases = getSelectedTestCase();
rootElem.setAttribute("count", Integer.toString(selectedTestCases.size()));
int from = 0;
int len = Integer.MAX_VALUE;
if (getFlags().containsKey("query")) {
final String queryStr = getFlags().get("query");
if (queryStr != null && !queryStr.isEmpty()) {
final Query query = new Query(queryStr);
try {
query.parse();
query.addToElement(rootElem);
} catch (final ParsingException e) {
query.addToElement(rootElem);
rootElem.addContent(ServerMessages.QUERY_PARSING_ERROR.toElement());
doc.setRootElement(rootElem);
return ok(doc);
}
}
}
if (getFlags().containsKey("P")) {
final String[] split = getFlags().get("P").split("L");
from = Integer.valueOf(split[0]);
len = Integer.valueOf(split[1]);
}
int i = 0;
for (final ToElementable e : selectedTestCases) {
if (i++ >= from && i <= from + len) {
e.addToElement(rootElem);
}
}
// testEntities = null;
// entityCounter = 1;
doc.setRootElement(rootElem);
return ok(doc);
}
private Collection<ToElementable> getSelectedTestCase() {
final HashMap<String, ToElementable> testCases = getTestCase();
if (getFlags().containsKey("query")) {
final String queryStr = getFlags().get("query");
if (queryStr.startsWith("FIND Annotation WHICH REFERENCES")) {
final LinkedList<ToElementable> ret = new LinkedList<ToElementable>();
for (final ToElementable e : testCases.values()) {
if (e instanceof Entity
&& ((Entity) e).hasParents()
&& ((Entity) e).getParents().get(0).getName().equals("Annotation")) {
ret.add(e);
}
}
return ret;
}
}
if (getFlags().containsKey("all")) {
final LinkedList<ToElementable> ret = new LinkedList<ToElementable>();
for (final ToElementable e : testCases.values()) {
if (e instanceof Entity) {
if (getFlags().get("all").equalsIgnoreCase("Entity")) {
ret.add(e);
} else {
final Entity entity = (Entity) e;
if (entity.getRole().toString().equalsIgnoreCase(getFlags().get("all"))) {
ret.add(e);
}
}
}
}
return ret;
}
if (!getRequestedIDs().isEmpty() || !getRequestedNames().isEmpty()) {
final LinkedList<ToElementable> ret = new LinkedList<ToElementable>();
for (final Integer id : getRequestedIDs()) {
for (final ToElementable e : testCases.values()) {
if (e instanceof Entity) {
if (((Entity) e).getId().equals(id)) {
ret.add(e);
}
}
}
}
for (final String name : getRequestedNames()) {
for (final ToElementable e : testCases.values()) {
if (e instanceof Entity) {
if (((Entity) e).getName().equals(name)) {
ret.add(e);
}
}
}
}
return ret;
}
return testCases.values();
}
private HashMap<String, ToElementable> getTestCase() {
if (testEntities == null) {
testEntities = new HashMap<String, ToElementable>();
final Entity lp1 =
createProperty(
"LIST"
+ AbstractCollectionDatatype.LEFT_DELIMITER
+ "TEXT"
+ AbstractCollectionDatatype.RIGHT_DELIMITER);
final Entity lp2 =
createProperty(
"LIST"
+ AbstractCollectionDatatype.LEFT_DELIMITER
+ "INTEGER"
+ AbstractCollectionDatatype.RIGHT_DELIMITER,
"meter");
final Entity lp4 =
createProperty(
"LIST"
+ AbstractCollectionDatatype.LEFT_DELIMITER
+ "BOOLEAN"
+ AbstractCollectionDatatype.RIGHT_DELIMITER);
final Entity tp1 = createProperty("TEXT");
final Entity dp1 = createProperty("DOUBLE", "meter");
final Entity dtp1 = createProperty("DATETIME");
final Entity tp2 = createProperty("TEXT");
final Entity ip1 = createProperty("INTEGER", "meter");
final Entity bp1 = createProperty("BOOLEAN");
final Entity fp1 = createProperty("FILE");
final Entity rt1 = createRecordType(new Entity[] {}, new Entity[] {});
final Entity rt2 = createRecordType(new Entity[] {}, new Entity[] {tp2, ip1, bp1});
final Entity rp1 = createProperty(rt2);
final Entity lp3 =
createProperty(
"LIST"
+ AbstractCollectionDatatype.LEFT_DELIMITER
+ rt2.getName()
+ AbstractCollectionDatatype.RIGHT_DELIMITER);
final Entity rt3 =
createRecordType(
new Entity[] {rt1}, new Entity[] {lp1, tp1, dp1, dtp1, rp1, lp2, lp3, lp4});
createRecordFromRecordType(rt2);
createRecordFromRecordType(rt3);
createFileRecord("lorem_ipsum.txt");
createFileRecord("testsubfolder/lorem_ipsum.txt");
final Entity pngFile = createFileRecord("testimg.png");
createFileRecord("testimg.jpg");
createFileRecord("testimg.svg");
createFileRecord("testimg.gif");
createRecordWithPngFile(pngFile, fp1, rt1);
/* Test Error, Warning, Info */
createError();
createWarning();
createInfo();
/* Entities with Error, Warning, Info */
createRecordFromRecordType(rt2)
.addError(
new Message(
MessageType.Error,
666,
"Description of this error",
"Further information about this error."));
createRecordFromRecordType(rt2)
.addWarning(
new Message(
MessageType.Warning,
346,
"Description of this warning",
"Further information about this warning."));
createRecordFromRecordType(rt2)
.addInfo(
new Message(
MessageType.Info,
0,
"Description of this info",
"Further information about this info."));
createAnnotation();
}
return testEntities;
}
private void createAnnotation() {
final Entity annotationOf = createEntity(Role.Property, "annotationOf");
annotationOf.setDatatype("REFERENCE");
final Entity comment = createEntity(Role.Property, "comment");
comment.setDatatype("TEXT");
final Entity annotation = createEntity(Role.RecordType, "Annotation");
annotation.addProperty(new Property(annotationOf));
annotation.addProperty(new Property(comment));
final Entity rec = createRecordFromRecordType(annotation);
rec.addTransactionLog(
new TransactionLogMessage(
"INSERT",
rec,
"Adam",
UTCDateTime.SystemMillisToUTCDateTime(System.currentTimeMillis())));
}
private Entity createRecordWithPngFile(
final Entity pngFile, final Entity property, final Entity parent) {
final Entity entity = createRecordFromRecordType(parent);
final Property newP = new Property(property.toElement());
newP.setValue(new ReferenceValue(pngFile));
entity.addProperty(newP);
return entity;
}
private Entity createFileRecord(final String path) {
final Entity entity = createEntity(Role.File, "TestFile");
entity.setFileProperties(TestCaseFileSystemResource.getFileProperties(path));
return entity;
}
private Entity createProperty(final Entity rt2) {
final Entity entity = createProperty("REFERENCE");
entity.setDatatype(rt2.getName());
return entity;
}
private Value generateValue(final AbstractDatatype dt) {
if (dt instanceof IntegerDatatype) {
return new GenericValue(1337);
} else if (dt instanceof DoubleDatatype) {
return new GenericValue(3.14);
} else if (dt instanceof DateTimeDatatype) {
return DateTimeFactory2.valueOf(new GregorianCalendar());
} else if (dt instanceof BooleanDatatype) {
return BooleanValue.valueOf(entityCounter % 2 == 0);
} else if (dt instanceof TextDatatype) {
return new GenericValue("Don't panic.");
} else if (dt instanceof ReferenceDatatype2) {
final ReferenceDatatype2 dt2 = (ReferenceDatatype2) dt;
dt2.getId();
for (final ToElementable e : testEntities.values()) {
if (e instanceof Entity && ((Entity) e).getRole() == Role.Record) {
return new ReferenceValue((Entity) e);
}
}
} else if (dt instanceof ListDatatype) {
final AbstractDatatype dt2 = ((ListDatatype) dt).getDatatype();
final CollectionValue v = new CollectionValue();
for (int i = 0; i < 25; i++) {
v.add(generateValue(dt2));
}
return v;
}
return null;
}
private Entity createRecordFromRecordType(final Entity rt2) {
final Entity entity = createEntity(Role.Record, "TestRecord");
addParentsAndProperties(entity, rt2, rt2.getProperties(), "FIX");
for (final Property p : entity.getProperties()) {
p.setValue(generateValue(p.getDatatype()));
}
return entity;
}
private void addParentsAndProperties(
final Entity entity,
final Entity parent,
final PropertyContainer properties,
final String importance) {
final List<Entity> par = new LinkedList<Entity>();
par.add(parent);
addParentsAndProperties(entity, par, properties, importance);
}
private void addParentsAndProperties(
final Entity entity,
final Collection<? extends EntityInterface> parents,
final Collection<? extends EntityInterface> properties,
final String importance) {
for (final EntityInterface p : properties) {
final Property newP = new Property(p.toElement());
if (importance != null) {
newP.setStatementStatus(StatementStatus.valueOf(importance));
}
entity.addProperty(newP);
}
for (final EntityInterface p : parents) {
entity.addParent(new Parent(p.toElement()));
}
}
private Message createError() {
return createMessage(
MessageType.Error.toString(),
"This is a description of the error",
"Here is additional info about the error.",
666);
}
private Message createWarning() {
return createMessage(
MessageType.Warning.toString(),
"This is a description of the warning",
"Here is additional info about the warning.",
333);
}
private Message createInfo() {
return createMessage(
MessageType.Info.toString(),
"Here is useful information about the last activity of the user.",
"Here is additional information.",
null);
}
private Message createMessage(
String type, final String description, final String body, final Integer code) {
final Message m = new Message(type, code, description, body);
if (testEntities.containsKey(type)) {
int i = 0;
while (testEntities.containsKey(type + "-" + i)) {
i++;
}
type += "-" + i;
}
testEntities.put(type, m);
return m;
}
private static HashMap<String, ToElementable> testEntities = null;
private static int entityCounter = 1;
private Entity createProperty(final String datatype) {
return createProperty(datatype, null);
}
private Entity createRecordType(final Entity[] parents, final Entity[] properties) {
final Entity entity = createEntity(Role.RecordType, "TestRecordType");
addParentsAndProperties(entity, parents, properties, "OBLIGATORY");
return entity;
}
private void addParentsAndProperties(
final Entity entity,
final EntityInterface[] parents,
final EntityInterface[] properties,
final String importance) {
addParentsAndProperties(entity, Arrays.asList(parents), Arrays.asList(properties), importance);
}
private Entity createProperty(final String datatype, final String unit) {
final Entity entity = createEntity(Role.Property, "Test" + datatype + "Property");
entity.setDatatype(datatype);
if (unit != null) {
final EntityInterface magicUnit = MagicTypes.UNIT.getEntity();
final Property unitProp = new Property();
unitProp.setId(magicUnit.getId());
unitProp.setValue(new GenericValue(unit));
entity.addProperty(unitProp);
}
return entity;
}
private Entity createEntity(final Role role, String name) {
final Entity entity = new RetrieveEntity(entityCounter++);
entity.setRole(role);
entity.setDescription(
"An informative description of the idea, meaning, and purpose of this entity.");
if (testEntities.containsKey(name)) {
int i = 0;
while (testEntities.containsKey(name + "-" + i)) {
i++;
}
name += "-" + i;
}
testEntities.put(name, entity);
entity.setName(name);
return entity;
}
}
......@@ -42,7 +42,6 @@ import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
......@@ -84,7 +83,7 @@ public class UserResource extends AbstractCaosDBServerResource {
}
doc.setRootElement(rootElem);
return new JdomRepresentation(doc, MediaType.TEXT_XML, " ", getReference(), getXSLScript());
return ok(doc);
}
@Override
......@@ -125,7 +124,7 @@ public class UserResource extends AbstractCaosDBServerResource {
rootElem.addContent(t.getUserElement());
doc.setRootElement(rootElem);
return new JdomRepresentation(doc, MediaType.TEXT_XML, " ", getReference(), getXSLScript());
return ok(doc);
} catch (final Message m) {
if (m == ServerMessages.ACCOUNT_DOES_NOT_EXIST) {
return error(m, Status.CLIENT_ERROR_NOT_FOUND);
......@@ -177,7 +176,7 @@ public class UserResource extends AbstractCaosDBServerResource {
rootElem.addContent(t.getUserElement());
doc.setRootElement(rootElem);
return new JdomRepresentation(doc, MediaType.TEXT_XML, " ", getReference(), getXSLScript());
return ok(doc);
}
@Override
......@@ -200,6 +199,6 @@ public class UserResource extends AbstractCaosDBServerResource {
rootElem.addContent(t.getUserElement());
doc.setRootElement(rootElem);
return new JdomRepresentation(doc, MediaType.TEXT_XML, " ", getReference(), getXSLScript());
return ok(doc);
}
}
......@@ -24,6 +24,7 @@ package caosdb.server.resource;
import caosdb.server.CaosDBServer;
import caosdb.server.ServerProperties;
import caosdb.server.utils.WebinterfaceUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
......@@ -42,13 +43,14 @@ import org.restlet.util.Series;
public class Webinterface extends ServerResource {
private WebinterfaceUtils utils;
@Override
protected void doInit() throws ResourceException {
this.utils = WebinterfaceUtils.getInstance(getHostRef());
super.doInit();
}
private static final File PUBLIC_DIRECTORY = new File("caosdb-webui/public/").getAbsoluteFile();
@Get
public Representation deliver() throws IOException {
final String path = (String) getRequest().getAttributes().get("path");
......@@ -57,23 +59,15 @@ public class Webinterface extends ServerResource {
getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
return null;
}
final File file = new File(this.PUBLIC_DIRECTORY.getAbsolutePath() + "/" + path);
// TODO if (!FileUtils.isSubDirSymlinkSave(file, this.PUBLIC_DIRECTORY.getParentFile())) {
// getResponse().setStatus(Status.CLIENT_ERROR_FORBIDDEN);
// return null;
// }
if (!file.exists()) {
final File file = utils.getPublicFile(path);
if (file == null) {
getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
return null;
}
Series<Header> headers = getRequest().getHeaders();
if (headers == null) {
headers = new Series<Header>(Header.class);
getResponse().getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, headers);
}
headers.set("Access-Control-Allow-Origin", getHostRef().toString());
// TODO move to FileUtils and use a third-party library
final MediaType mt =
path.endsWith(".json")
? MediaType.APPLICATION_JSON
......@@ -91,6 +85,13 @@ public class Webinterface extends ServerResource {
final FileRepresentation ret = new FileRepresentation(file, mt);
Series<Header> headers = getRequest().getHeaders();
if (headers == null) {
headers = new Series<Header>(Header.class);
getResponse().getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, headers);
}
headers.set("Access-Control-Allow-Origin", getHostRef().toString());
List<CacheDirective> cacheDirectives = new ArrayList<>();
cacheDirectives.add(
new CacheDirective(
......
/**
* ** header v3.0 This file is a part of the CaosDB Project.
*
* <p>Copyright (c) 2019 IndiScale GmbH Copyright (c) 2019 Daniel Hornung <d.hornung@indiscale.com>
*
* <p>This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* <p>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 Affero General Public License for more details.
*
* <p>You should have received a copy of the GNU Affero General Public License along with this
* program. If not, see <https://www.gnu.org/licenses/>.
*
* <p>** end header
*/
package caosdb.server.resource;
import caosdb.server.utils.WebinterfaceUtils;
import org.restlet.data.Status;
import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;
/**
* This {@link ServerResource} exposes the current build number of the web interface.
*
* <p>It is mainly used for testing and debugging. A User can determine the current build number of
* the web interface.
*
* @author Timm Fitschen (t.fitschen@indiscale.com)
*/
public class WebinterfaceBuildNumber extends ServerResource {
private WebinterfaceUtils utils;
public WebinterfaceBuildNumber() {
this.utils = WebinterfaceUtils.getInstance(getHostRef());
}
/**
* Return the current build number of the web interface.
*
* <p>If the build number could not be determined the server responds with 404 - Not Found.
* Reasons for this include an out-dated web interface, the web interface has not been build yet
* or the web interface is not available at all.
*
* @return the current build number.
*/
@Get("text")
public String getBuildNumber() {
String buildNumber = utils.getBuildNumber();
if (buildNumber == null) {
setStatus(Status.CLIENT_ERROR_NOT_FOUND, "Build number could not be determined.");
}
return buildNumber;
}
}
......@@ -29,7 +29,6 @@ import caosdb.server.entity.container.InsertContainer;
import caosdb.server.entity.container.RetrieveContainer;
import caosdb.server.entity.container.UpdateContainer;
import caosdb.server.resource.AbstractCaosDBServerResource;
import caosdb.server.resource.JdomRepresentation;
import caosdb.server.resource.transaction.handlers.FileUploadHandler;
import caosdb.server.resource.transaction.handlers.IDHandler;
import caosdb.server.resource.transaction.handlers.RequestHandler;
......@@ -120,7 +119,7 @@ public class EntityResource extends AbstractCaosDBServerResource {
final Element rootElem = generateRootElement();
entityContainer.addToElement(rootElem);
doc.setRootElement(rootElem);
return new JdomRepresentation(doc, MediaType.TEXT_XML, " ", getReference(), getXSLScript());
return ok(doc);
}
@Override
......@@ -143,7 +142,7 @@ public class EntityResource extends AbstractCaosDBServerResource {
entityContainer.addToElement(rootElem);
doc.setRootElement(rootElem);
return new JdomRepresentation(doc, MediaType.TEXT_XML, " ", getReference(), getXSLScript());
return ok(doc);
}
@Override
......@@ -167,7 +166,8 @@ public class EntityResource extends AbstractCaosDBServerResource {
final Element rootElem = generateRootElement();
entityContainer.addToElement(rootElem);
doc.setRootElement(rootElem);
return new JdomRepresentation(doc, MediaType.TEXT_XML, " ", getReference(), getXSLScript());
return ok(doc);
}
@Override
......@@ -191,6 +191,7 @@ public class EntityResource extends AbstractCaosDBServerResource {
final Element rootElem = generateRootElement();
entityContainer.addToElement(rootElem);
doc.setRootElement(rootElem);
return new JdomRepresentation(doc, MediaType.TEXT_XML, " ", getReference(), getXSLScript());
return ok(doc);
}
}
......@@ -191,7 +191,6 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
this.getClass().getSimpleName() + ".commit", System.currentTimeMillis() - t1);
} catch (final Exception e) {
e.printStackTrace();
rollBack();
throw e;
} finally {
......
/**
* ** header v3.0 This file is a part of the CaosDB Project.
*
* <p>Copyright (c) 2019 IndiScale GmbH Copyright (c) 2019 Daniel Hornung <d.hornung@indiscale.com>
*
* <p>This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* <p>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 Affero General Public License for more details.
*
* <p>You should have received a copy of the GNU Affero General Public License along with this
* program. If not, see <https://www.gnu.org/licenses/>.
*
* <p>** end header
*/
package caosdb.server.utils;
import caosdb.server.CaosDBServer;
import caosdb.server.ServerProperties;
import caosdb.server.resource.AbstractCaosDBServerResource;
import caosdb.server.resource.Webinterface;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.restlet.data.Reference;
/**
* This class is responsible for two things:
* <li>Resolving relative paths of files of the web interface to {@link File} objects.
* <li>Generating URIs to public files of the web interface from relative string paths.
*
* <p>Especially the {@link AbstractCaosDBServerResource} and the {@link Webinterface} use this
* class for these tasks.
*
* @author Timm Fitschen (t.fitschen@indiscale.com)
*/
public class WebinterfaceUtils {
private Logger logger = LogManager.getLogger(this.getClass());
public static final String DEFAULT_WEBUI_PUBLIC = "caosdb-webui/public";
public static final String DEFAULT_BUILD_NUMBER_FILE = ".build_number";
public static final String DEFAULT_ROUTE = "webinterface";
private final String host;
private final String publicDir;
private final String route;
private final Path buildNumberFile;
private final String contextRoot;
private String buildNumber = null;
private long buildNumberDate;
private static final Map<String, WebinterfaceUtils> instances = new HashMap<>();
/**
* Retrieve an instance of {@link WebinterfaceUtils} for the host. The instance can be shared with
* other callers.
*
* @param host
* @return a shared instance of {@link WebinterfaceUtils}.
*/
public static WebinterfaceUtils getInstance(Reference host) {
return getInstance(host.getHostIdentifier());
}
/**
* Retrieve an instance of {@link WebinterfaceUtils} for the host. The instance can be shared with
* other callers.
*
* @param host
* @return a shared instance of {@link WebinterfaceUtils}.
*/
public static WebinterfaceUtils getInstance(String host) {
synchronized (instances) {
WebinterfaceUtils ret = instances.get(host);
if (ret == null) {
ret = new WebinterfaceUtils(host);
instances.put(host, ret);
}
return ret;
}
}
/**
* Create a utility class for creating references to resources of the web interface.
*
* @param host
* @param contextRoot
* @param publicDir
* @param route
* @param buildNumberFile
*/
WebinterfaceUtils(
String host, String contextRoot, String publicDir, String route, String buildNumberFile) {
this.host = host;
this.contextRoot = contextRoot;
this.publicDir = publicDir;
this.route = route;
this.buildNumberFile = getPublicRootPath().resolve(buildNumberFile);
}
/**
* Create a utility class for creating references to resources of the web interface.
*
* <p>The route to the web interface, the build number file and the public folder of the web
* interface build directory are set to default values ({@link #DEFAULT_BUILD_NUMBER_FILE}, {@link
* #DEFAULT_ROUTE}, and {@link #DEFAULT_WEBUI_PUBLIC}).
*
* <p>The contextRoute defaults to the server property {@link ServerProperties.KEY_CONTEXT_ROOT}.
*
* @param host - the host of the web interface.
* @see {@link WebinterfaceUtils#WebinterfaceUtils(String, String, String, String)}.
*/
WebinterfaceUtils(Reference host) {
this(host.getHostIdentifier());
}
/**
* Create a utility class for creating references to resources of the web interface.
*
* <p>The route to the web interface, the build number file and the public folder of the web
* interface build directory are set to default values ({@link #DEFAULT_BUILD_NUMBER_FILE}, {@link
* #DEFAULT_ROUTE}, and {@link #DEFAULT_WEBUI_PUBLIC}).
*
* <p>The contextRoute defaults to the server property {@link ServerProperties.KEY_CONTEXT_ROOT}.
*
* @param host - the host of the web interface.
* @see {@link WebinterfaceUtils#WebinterfaceUtils(String, String, String, String)}.
*/
WebinterfaceUtils(String host) {
this(
host,
CaosDBServer.getServerProperty(ServerProperties.KEY_CONTEXT_ROOT),
DEFAULT_WEBUI_PUBLIC,
DEFAULT_ROUTE,
DEFAULT_BUILD_NUMBER_FILE);
}
/**
* Return the server root reference.
*
* <p>The server root is composed of the scheme (https://) the host name (example.com) the port if
* necessary (:10443) and the context root of the application (beta).
*
* @return the server root reference e.g. https://example.com:10443/beta.
*/
public String getServerRootURI() {
if (contextRoot == null || contextRoot.length() == 0) {
return host;
}
return host + "/" + contextRoot;
}
/**
* Return the root reference for the web interface.
*
* <p>The root reference for the web interface is the server root reference ({@link
* #getServerRootURI()}) plus the route to the resource which routes to the individual resources
* of the web interface ({@link Webinterface}).
*
* @return root reference for the web interface.
*/
public String getWebinterfaceRootURI() {
return getServerRootURI() + "/" + this.route + "/" + getBuildNumber();
}
/**
* Return the absolute path of the root of the public directory of the webui.
*
* @return Public directory of webui.
*/
public Path getPublicRootPath() {
return Paths.get(this.publicDir).toAbsolutePath();
}
/**
* return the Path of the build number file.
*
* @return
*/
public Path getBuildNumberFile() {
return this.buildNumberFile;
}
/**
* Determine the build number of the webui. Returns null if the build number could not be
* determined.
*
* <p>Because opening and reading the {@link #buildNumberFile} content is expensive, the
* buildNumber is cached.
*
* <p>The cached buildNumber invalidates when the files modified time stamp is younger than the
* {@link #buildNumberDate}. The validity of the cache is checked once every minute with a {@link
* CronJob}.
*
* @return Build number or null.
*/
public String getBuildNumber() {
validateBuildNumber();
if (this.buildNumber == null) {
this.buildNumberDate = System.currentTimeMillis();
this.buildNumber = readBuildNumber();
}
return this.buildNumber;
}
/**
* Return the latest buildNumber, freshly read from the buildNumberfile.
*
* @return fresh build number.
*/
public String readBuildNumber() {
Path buildNumberFile = getBuildNumberFile();
StringBuilder contentBuilder = new StringBuilder();
try (Stream<String> stream = Files.lines(buildNumberFile, StandardCharsets.UTF_8)) {
stream.forEach(s -> contentBuilder.append(s));
} catch (IOException e) {
logger.error(e);
return null;
}
return contentBuilder.toString();
}
/**
* Return the public file or null if the file does not exists or is not in the public directory of
* the web interface.
*
* <p>This method is used to resolve requests for files of the web interface.
*
* @param path
* @return The file or null
*/
public File getPublicFile(String path) {
Path publicFilePath = getPublicFilePath(path);
if (publicFilePath != null) {
File publicFile = publicFilePath.toFile();
if (publicFile.exists() && !publicFile.isDirectory() && publicFile.canRead())
return publicFile;
}
return null;
}
/**
* Return the absolute path to the public file denoted by the path.
*
* <p>This method is used to resolve requests for files of the web interface.
*
* @param path - path to the file of the web interface, relative to the public directory of the
* web interface.
* @return The absolute path of the web interface file.
*/
public Path getPublicFilePath(String path) {
Path root = getPublicRootPath();
Path resolved = root.resolve(path).toAbsolutePath().normalize();
if (resolved.startsWith(root)) {
return resolved;
}
return null;
}
/**
* Return a URI to a public file of the web interface.
*
* <p>This method is used to generate references for *.xsl files or other resources of the web
* interface.
*
* @param publicFile - the path of the public file, relative to the public directory of the web
* interface.
* @return a URI for the public file of the web interface.
*/
public String getWebinterfaceURI(String publicFile) {
return getWebinterfaceRootURI() + "/" + publicFile;
}
/**
* Check the validity of the buildNumber and invalidate the buildNumber if the buildNumberDate is
* older than the {@link File#lastModified} time stamp of the buildNumberFile.
*/
public void validateBuildNumber() {
if (buildNumber != null
&& getBuildNumberFile().toFile().lastModified() > this.buildNumberDate) {
this.buildNumber = null;
}
}
}
......@@ -178,6 +178,7 @@ public class TestScriptingResource {
request.setRootRef(new Reference("bla"));
request.getAttributes().put("SRID", "asdf1234");
request.setDate(new Date());
request.setHostRef("bla");
resource.init(null, request, new Response(null));
resource.handle();
......
package caosdb.server.utils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import caosdb.server.CaosDBServer;
import java.io.IOException;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.restlet.data.Reference;
public class WebinterfaceUtilsTest {
@Rule public ExpectedException exceptionRule = ExpectedException.none();
@BeforeClass
public static void setup() throws IOException {
CaosDBServer.initServerProperties();
}
@Test
public void testGetWebinterfaceReference() {
WebinterfaceUtils utils = new WebinterfaceUtils(new Reference("https://host:2345/some_path"));
String buildNumber = utils.getBuildNumber();
String ref = utils.getWebinterfaceURI("sub");
assertEquals("https://host:2345/webinterface/" + buildNumber + "/sub", ref);
}
@Test
public void testGetPublicFile() {
WebinterfaceUtils utils = new WebinterfaceUtils(new Reference("https://host:2345/some_path"));
assertNull(utils.getPublicFile("../"));
}
@Test
public void testGetPublicFilePath() {
WebinterfaceUtils utils = new WebinterfaceUtils(new Reference("https://host:2345/some_path"));
assertNull(utils.getPublicFilePath("../"));
assertNotNull(utils.getPublicFilePath("./"));
assertNotNull(utils.getPublicFilePath("bla"));
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment