diff --git a/CHANGELOG.md b/CHANGELOG.md index ea88e321261634711f9e084f318adf497672d66c..7c8626619dd6f48633591835e485561c73113d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* Missing handling of list of reference properties in SELECT queries. * #51 - name queries (e.g. `FIND ENTITY WITH name = ...`) - #27 - star matches slashes (e.g. for `FIND ... STORED AT /*.dat`). - #30 - file path cannot be in quotes diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTransaction.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTransaction.java index f65e319b7e002064154706abde3482b88d2a7b6b..6bac0ff187fe2a08d2d0c1c7450ac954bc023d5e 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTransaction.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTransaction.java @@ -1,20 +1,24 @@ /* - * ** header v3.0 This file is a part of the CaosDB Project. + * ** 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 Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> Copyright - * (C) 2020 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2018 Research Group Biomedical Physics, + * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2020-2021 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2020-2021 Timm Fitschen <t.fitschen@indiscale.com> * - * 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 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. + * 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/>. + * 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 */ @@ -24,7 +28,8 @@ import java.util.LinkedList; import java.util.List; import org.caosdb.server.database.BackendTransaction; import org.caosdb.server.database.exceptions.EntityDoesNotExistException; -import org.caosdb.server.datatype.ReferenceDatatype; +import org.caosdb.server.datatype.CollectionValue; +import org.caosdb.server.datatype.IndexedSingleValue; import org.caosdb.server.datatype.ReferenceValue; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.Message; @@ -113,6 +118,53 @@ public class RetrieveFullEntityTransaction extends BackendTransaction { } } + /** + * Recursively resolve the reference values of the list of reference property `p` (but only the + * selected sub-properties). + */ + private void resolveReferenceListProperty( + Property p, List<Selection> selections, String propertyName) { + try { + p.parseValue(); + } catch (Message m) { + p.addError(m); + } + + CollectionValue values = (CollectionValue) p.getValue(); + for (IndexedSingleValue sv : values) { + resolveReferenceValue((ReferenceValue) sv.getWrapped(), selections, propertyName); + } + } + + /** + * Recursively resolve the reference values of the reference property `p` (but only the selected + * sub-properties). + */ + private void resolveReferenceProperty( + Property p, List<Selection> selections, String propertyName) { + try { + p.parseValue(); + } catch (Message m) { + p.addError(m); + } + + resolveReferenceValue((ReferenceValue) p.getValue(), selections, propertyName); + } + + /** + * Recursively resolve the reference value. + * + * <p>To be called by {@link #resolveReferenceListProperty(Property, List, String)} and {@link + * #resolveReferenceProperty(Property, List, String)}. + */ + private void resolveReferenceValue( + ReferenceValue value, List<Selection> selections, String propertyName) { + RetrieveEntity ref = new RetrieveEntity(value.getId()); + // recursion! (Only for the matching selections) + retrieveFullEntity(ref, getSubSelects(selections, propertyName)); + value.setEntity(ref, true); + } + /** * Retrieve the Entities which match the selections and are referenced by the Entity 'e'. * @@ -122,55 +174,47 @@ public class RetrieveFullEntityTransaction extends BackendTransaction { public void retrieveSubEntities(EntityInterface e, List<Selection> selections) { for (final Selection s : selections) { String propertyName = s.getSelector(); - if (s.getSubselection() != null) { - - // Find matching (i.e. referencing) Properties - for (Property p : e.getProperties()) { - // get reference properties by name. - if (propertyName.equalsIgnoreCase(p.getName()) - && p.getDatatype() instanceof ReferenceDatatype) { - if (p.getValue() != null) { - try { - p.parseValue(); - } catch (Message m) { - p.addError(m); + for (Property p : e.getProperties()) { + if (s.getSubselection() != null) { + // The presence of sub-selections means that the properties are + // expected to be references (list or plain). + if (p.getValue() != null) { + if (propertyName.equalsIgnoreCase(p.getName())) { + if (p.isReference()) { + // handle (single) reference properties with matching name... + resolveReferenceProperty(p, selections, propertyName); + continue; + } else if (p.isReferenceList()) { + // handle (list) reference properties with matching name... + resolveReferenceListProperty(p, selections, propertyName); + continue; } - - ReferenceValue value = (ReferenceValue) p.getValue(); - RetrieveEntity ref = new RetrieveEntity(value.getId()); - // recursion! (Only for the matching selections) - retrieveFullEntity(ref, getSubSelects(selections, propertyName)); - value.setEntity(ref, true); - } - continue; - } - try { - boolean isSubtype = execute(new IsSubType(p.getId(), propertyName)).isSubType(); - if (isSubtype) { - if (p.getValue() != null) { - if (p.getDatatype() instanceof ReferenceDatatype) { - try { - p.parseValue(); - } catch (Message m) { - p.addError(m); + } else { + try { + boolean isSubtype = execute(new IsSubType(p.getId(), propertyName)).isSubType(); + if (isSubtype) { + // ... handle reference properties that are a subtype of `propertyName`. + if (p.getValue() != null) { + if (p.isReference()) { + resolveReferenceProperty(p, selections, propertyName); + } else if (p.isReferenceList()) { + resolveReferenceListProperty(p, selections, propertyName); + } } - ReferenceValue value = (ReferenceValue) p.getValue(); - RetrieveEntity ref = new RetrieveEntity(value.getId()); - // recursion! (Only for the matching selections) - retrieveFullEntity(ref, getSubSelects(selections, propertyName)); - value.setEntity(ref, true); + // the name is set the the super-types name! Otherwise, clients + // wouldn't know what this property is supposed to be. p.setName(propertyName); } + } catch (EntityDoesNotExistException exc) { + // unknown parent name. } } - } catch (EntityDoesNotExistException exc) { - // unknown parent name. } - } - } else { - for (Property p : e.getProperties()) { + } else { + // no subselections - no need to resolve any references... if (!propertyName.equalsIgnoreCase(p.getName())) { + // ... we only need to cover property sub-typing try { boolean isSubtype = execute(new IsSubType(p.getId(), propertyName)).isSubType(); if (isSubtype) { diff --git a/src/main/java/org/caosdb/server/entity/Entity.java b/src/main/java/org/caosdb/server/entity/Entity.java index 845bb25a3cee665fd8f0985c6969f53dfc868025..aa4c96127cfc97b03f308a50cb587dc12949c105 100644 --- a/src/main/java/org/caosdb/server/entity/Entity.java +++ b/src/main/java/org/caosdb/server/entity/Entity.java @@ -41,6 +41,7 @@ import org.caosdb.server.datatype.AbstractCollectionDatatype; import org.caosdb.server.datatype.AbstractDatatype; import org.caosdb.server.datatype.CollectionValue; import org.caosdb.server.datatype.GenericValue; +import org.caosdb.server.datatype.ReferenceDatatype; import org.caosdb.server.datatype.Value; import org.caosdb.server.entity.Message.MessageType; import org.caosdb.server.entity.container.ParentContainer; @@ -1159,4 +1160,16 @@ public class Entity extends AbstractObservable implements EntityInterface { } return getId().toString(); } + + @Override + public boolean isReference() { + return this.hasDatatype() && this.getDatatype() instanceof ReferenceDatatype; + } + + @Override + public boolean isReferenceList() { + return this.hasDatatype() + && this.getDatatype() instanceof AbstractCollectionDatatype + && ((AbstractCollectionDatatype) getDatatype()).getDatatype() instanceof ReferenceDatatype; + } } diff --git a/src/main/java/org/caosdb/server/entity/EntityInterface.java b/src/main/java/org/caosdb/server/entity/EntityInterface.java index ef97630808536f367869851c5be06d05e6247ac2..954bcc47ca5b6117d3164ba3c9c585dbd373d4f8 100644 --- a/src/main/java/org/caosdb/server/entity/EntityInterface.java +++ b/src/main/java/org/caosdb/server/entity/EntityInterface.java @@ -194,4 +194,13 @@ public interface EntityInterface public abstract void setVersion(Version version); public abstract void addToElement(Element element, SetFieldStrategy strategy); + + /** Return true iff the data type is present and is an instance of ReferenceDatatype. */ + public abstract boolean isReference(); + + /** + * Return true iff the data type is present, an instance of AbstractCollectionDatatype and the + * AbstractCollectionDatatype's elements' data type is an instance of ReferenceDatatype. + */ + public abstract boolean isReferenceList(); } diff --git a/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java b/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java index e042c2326cc43e5d98cc3a27b61c21301bf9e90d..c83cb67883f5a616109268fb6d62bae40cd73e6b 100644 --- a/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java +++ b/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java @@ -580,4 +580,14 @@ public class EntityWrapper implements EntityInterface { public void addToElement(Element element, SetFieldStrategy strategy) { this.entity.addToElement(element, strategy); } + + @Override + public boolean isReference() { + return this.entity.isReference(); + } + + @Override + public boolean isReferenceList() { + return this.entity.isReferenceList(); + } } diff --git a/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java b/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java index a829d711aa6345a16c9c368db27cb2f146f044b8..89420c596894da9be35e33a507821c96d10f5b7d 100644 --- a/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java +++ b/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java @@ -4,8 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> - * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2020-2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2020-2021 IndiScale GmbH <info@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -25,6 +25,8 @@ package org.caosdb.server.entity.xml; import org.apache.shiro.SecurityUtils; +import org.caosdb.server.datatype.CollectionValue; +import org.caosdb.server.datatype.IndexedSingleValue; import org.caosdb.server.datatype.ReferenceValue; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.Message; @@ -137,8 +139,7 @@ public class EntityToElementStrategy implements ToElementStrategy { // CheckValueParsable job. } - if (entity.getValue() instanceof ReferenceValue - && setFieldStrategy.isToBeSet("_referenced")) { + if (entity.isReference() && setFieldStrategy.isToBeSet("_referenced")) { // Append the complete entity. This needs to be done when we are // processing SELECT Queries. EntityInterface ref = ((ReferenceValue) entity.getValue()).getEntity(); @@ -151,6 +152,26 @@ public class EntityToElementStrategy implements ToElementStrategy { // adding the reference id as well. return; } + } else if (entity.isReferenceList() && setFieldStrategy.isToBeSet("_referenced")) { + // Append the all referenced entities. This needs to be done when we are + // processing SELECT Queries. + boolean skipValue = false; + for (IndexedSingleValue sv : ((CollectionValue) entity.getValue())) { + EntityInterface ref = ((ReferenceValue) sv.getWrapped()).getEntity(); + if (ref != null) { + if (entity.hasDatatype()) { + setDatatype(entity, element); + } + Element valueElem = new Element("Value"); + ref.addToElement(valueElem, setFieldStrategy); + element.addContent(valueElem); + skipValue = true; + } + } + if (skipValue) + // the referenced entity has been appended. Return here to suppress + // adding the reference id as well. + return; } if (setFieldStrategy.isToBeSet("value")) { diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java b/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java index 42476f87b43496e162b6eb036480b6b745ee91f2..c9d06cb5487ff62a276ff1028c22864afa850562 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java @@ -4,8 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com> - * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2020-2021 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2020-2021 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -26,10 +26,8 @@ package org.caosdb.server.jobs.core; import org.caosdb.server.database.exceptions.EntityDoesNotExistException; import org.caosdb.server.database.exceptions.EntityWasNotUniqueException; -import org.caosdb.server.datatype.AbstractCollectionDatatype; import org.caosdb.server.datatype.CollectionValue; import org.caosdb.server.datatype.IndexedSingleValue; -import org.caosdb.server.datatype.ReferenceDatatype; import org.caosdb.server.datatype.ReferenceValue; import org.caosdb.server.entity.Entity; import org.caosdb.server.entity.EntityInterface; @@ -49,7 +47,7 @@ public class CheckRefidValid extends EntityJob { @Override public final void run() { try { - if (isReference(getEntity())) { + if (assureReference(getEntity())) { if (getEntity().hasValue()) { // parse referenced id @@ -58,11 +56,9 @@ public class CheckRefidValid extends EntityJob { return; } - if (getEntity().getDatatype() instanceof ReferenceDatatype) { + if (getEntity().isReference()) { checkRefValue((ReferenceValue) getEntity().getValue()); - } else if (getEntity().getDatatype() instanceof AbstractCollectionDatatype - && ((AbstractCollectionDatatype) getEntity().getDatatype()).getDatatype() - instanceof ReferenceDatatype) { + } else if (getEntity().isReferenceList()) { final CollectionValue vals = (CollectionValue) getEntity().getValue(); for (final IndexedSingleValue v : vals) { if (v != null && v.getWrapped() != null) { @@ -175,14 +171,15 @@ public class CheckRefidValid extends EntityJob { return true; } - private final boolean isReference(final EntityInterface entity) { + /** + * Return true if this is a reference or a list of reference property. + * + * <p>If the data type is not present (yet), append a data type listener which calls this job + * again when the data type is present. + */ + private final boolean assureReference(final EntityInterface entity) { if (entity.hasDatatype()) { - if (entity.getDatatype() instanceof ReferenceDatatype) { - return true; - } else if (entity.getDatatype() instanceof AbstractCollectionDatatype) { - return ((AbstractCollectionDatatype) entity.getDatatype()).getDatatype() - instanceof ReferenceDatatype; - } + return entity.isReference() || entity.isReferenceList(); } else { entity.acceptObserver(this); } diff --git a/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java b/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java index c7ecd4deb76117474adc188c3f8a7d0f6064a3b8..876b43a160f673b96a7220a746c9a7dcd4951388 100644 --- a/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java +++ b/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java @@ -117,7 +117,7 @@ public abstract class AbstractCaosDBServerResource extends ServerResource { getRequest().setEntity(r); } - this.utils = WebinterfaceUtils.getInstance(getHostRef()); + this.utils = WebinterfaceUtils.getInstance(getRequest()); this.timestamp = getRequest().getDate().getTime(); @@ -177,7 +177,7 @@ public abstract class AbstractCaosDBServerResource extends ServerResource { retRoot.setAttribute("crid", this.getCRID()); } retRoot.setAttribute("timestamp", getTimestamp().toString()); - retRoot.setAttribute("baseuri", getRootRef().toString()); + retRoot.setAttribute("baseuri", getUtils().getServerRootURI()); return retRoot; } diff --git a/src/main/java/org/caosdb/server/resource/Webinterface.java b/src/main/java/org/caosdb/server/resource/Webinterface.java index bba1fb09c7f2c0a14b95b7314832b6ff174cef6b..adba714a96e5bbb4bc3ea13b8d72ec69a88cb33b 100644 --- a/src/main/java/org/caosdb/server/resource/Webinterface.java +++ b/src/main/java/org/caosdb/server/resource/Webinterface.java @@ -47,7 +47,7 @@ public class Webinterface extends ServerResource { @Override protected void doInit() throws ResourceException { - this.utils = WebinterfaceUtils.getInstance(getHostRef()); + this.utils = WebinterfaceUtils.getInstance(getRequest()); super.doInit(); } diff --git a/src/main/java/org/caosdb/server/resource/WebinterfaceBuildNumber.java b/src/main/java/org/caosdb/server/resource/WebinterfaceBuildNumber.java index 0fe186a0426002071e0b6c6a5ebe0b3f234e9b2a..22f1a46e4fd314b0dcaebde7abdcc1d41a9f50f0 100644 --- a/src/main/java/org/caosdb/server/resource/WebinterfaceBuildNumber.java +++ b/src/main/java/org/caosdb/server/resource/WebinterfaceBuildNumber.java @@ -39,7 +39,7 @@ public class WebinterfaceBuildNumber extends ServerResource { @Override protected void doInit() throws ResourceException { super.doInit(); - this.utils = WebinterfaceUtils.getInstance(getHostRef()); + this.utils = WebinterfaceUtils.getInstance(getRequest()); } /** diff --git a/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java b/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java index 1aff4de329bf3f32ba599a449c101b7fbe30423f..375d6b5e7bbced84cec5bdcdee26a9e436f0215a 100644 --- a/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java +++ b/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java @@ -33,6 +33,7 @@ import org.caosdb.server.CaosDBServer; import org.caosdb.server.ServerProperties; import org.caosdb.server.resource.AbstractCaosDBServerResource; import org.caosdb.server.resource.Webinterface; +import org.restlet.Request; import org.restlet.data.Reference; /** @@ -61,14 +62,19 @@ public class WebinterfaceUtils { 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. + * Retrieve an instance of {@link WebinterfaceUtils} for the request. The instance can be shared + * with other callers. * - * @param host + * @param request * @return a shared instance of {@link WebinterfaceUtils}. */ - public static WebinterfaceUtils getInstance(Reference host) { - return getInstance(host.getHostIdentifier()); + public static WebinterfaceUtils getInstance(Request request) { + String hostStr = request.getHostRef().getHostIdentifier(); + String scheme = request.getHeaders().getFirstValue("X-Forwarded-Proto", true); + if (scheme != null) { + hostStr = hostStr.replaceFirst("^" + request.getHostRef().getScheme(), scheme); + } + return getInstance(hostStr); } /** diff --git a/src/test/java/org/caosdb/server/entity/EntityTest.java b/src/test/java/org/caosdb/server/entity/EntityTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3bc85dfbf81ba865e2ebb543a28b72c5e468a485 --- /dev/null +++ b/src/test/java/org/caosdb/server/entity/EntityTest.java @@ -0,0 +1,59 @@ +/* + * ** header v3.0 + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * + * 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 org.caosdb.server.entity; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.caosdb.server.datatype.IntegerDatatype; +import org.junit.Test; + +public class EntityTest { + + @Test + public void testIsReference() { + EntityInterface entity = new RetrieveEntity("test"); + assertFalse(entity.isReference()); + + entity.setDatatype(new IntegerDatatype()); + assertFalse(entity.isReference()); + + entity.setDatatype("Person"); + assertTrue(entity.isReference()); + } + + @Test + public void testIsReferenceList() { + EntityInterface entity = new RetrieveEntity("test"); + assertFalse(entity.isReferenceList()); + + entity.setDatatype(new IntegerDatatype()); + assertFalse(entity.isReferenceList()); + + entity.setDatatype("Person"); + assertFalse(entity.isReferenceList()); + + entity.setDatatype("LIST<Person>"); + assertTrue(entity.isReferenceList()); + } +} diff --git a/src/test/java/org/caosdb/server/entity/xml/PropertyToElementStrategyTest.java b/src/test/java/org/caosdb/server/entity/xml/PropertyToElementStrategyTest.java index 99cf2706937dce10b8db3a1305053356587e139a..436278ad640663ba42f71b824924b2f1293a5132 100644 --- a/src/test/java/org/caosdb/server/entity/xml/PropertyToElementStrategyTest.java +++ b/src/test/java/org/caosdb/server/entity/xml/PropertyToElementStrategyTest.java @@ -77,6 +77,7 @@ public class PropertyToElementStrategyTest { house.addProperty(houseHeight); windowProperty = new Property(2345); windowProperty.setName("window"); + windowProperty.setDatatype("window"); windowProperty.setValue(new ReferenceValue(window.getId())); house.addProperty(windowProperty); diff --git a/src/test/java/org/caosdb/server/resource/TestAbstractCaosDBServerResource.java b/src/test/java/org/caosdb/server/resource/TestAbstractCaosDBServerResource.java index 1dcf71d8f330ab6807e65bd23022138e941fca1c..5d81ca738d507bcfd75dbe137756135cede3989b 100644 --- a/src/test/java/org/caosdb/server/resource/TestAbstractCaosDBServerResource.java +++ b/src/test/java/org/caosdb/server/resource/TestAbstractCaosDBServerResource.java @@ -26,6 +26,7 @@ import org.caosdb.server.database.backend.interfaces.RetrieveRoleImpl; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.misc.TransactionBenchmark; import org.caosdb.server.permissions.PermissionRule; +import org.caosdb.server.utils.WebinterfaceUtils; import org.jdom2.Element; import org.junit.BeforeClass; import org.junit.Rule; @@ -125,6 +126,11 @@ public class TestAbstractCaosDBServerResource { // TODO Auto-generated method stub return user; } + + @Override + public WebinterfaceUtils getUtils() { + return WebinterfaceUtils.getInstance(getRootRef().toString()); + } }; provideUserSourcesFile(); Element response = s.generateRootElement();