From e3fa461159f0301a951955f5b70d343838285854 Mon Sep 17 00:00:00 2001 From: felmer <franz-josef.elmer@id.ethz.ch> Date: Wed, 6 Feb 2019 12:03:13 +0100 Subject: [PATCH] SSDM-7857: bug in DataSetDeliverer fixed: didn't created x:contentCopy tags. Provide about.xml, capabilitylist.xml --- .../datasource/AbstractEntityDeliverer.java | 5 +- .../sync/datasource/DataSetDeliverer.java | 2 + .../datasource/DataSourceRequestHandler.java | 202 +++++++++++------- .../sync/datasource/DataSourceUtils.java | 60 +++++- .../plugins/sync/datasource/Deliverers.java | 48 +++++ .../plugins/sync/datasource/IDeliverer.java | 35 +++ .../sync/datasource/MasterDataDeliverer.java | 2 +- 7 files changed, 267 insertions(+), 87 deletions(-) create mode 100644 datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/Deliverers.java create mode 100644 datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/IDeliverer.java diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/AbstractEntityDeliverer.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/AbstractEntityDeliverer.java index 75e9c14e8c1..29c1ae70193 100644 --- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/AbstractEntityDeliverer.java +++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/AbstractEntityDeliverer.java @@ -48,7 +48,7 @@ import ch.systemsx.cisd.openbis.generic.server.batch.IBatchOperation; /** * @author Franz-Josef Elmer */ -abstract class AbstractEntityDeliverer<T> +abstract class AbstractEntityDeliverer<T> implements IDeliverer { private static final int CHUNK_SIZE = 1000; @@ -70,7 +70,8 @@ abstract class AbstractEntityDeliverer<T> operationLog = LogFactory.getLogger(LogCategory.OPERATION, getClass()); } - void deliverEntities(XMLStreamWriter writer, String sessionToken, Set<String> spaces, Date requestTimestamp) throws XMLStreamException + @Override + public void deliverEntities(XMLStreamWriter writer, String sessionToken, Set<String> spaces, Date requestTimestamp) throws XMLStreamException { List<T> allEntities = getAllEntities(sessionToken); executeInBatches(allEntities, entities -> deliverEntities(writer, sessionToken, spaces, entities)); diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/DataSetDeliverer.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/DataSetDeliverer.java index 53892dee278..4b93d9118f6 100644 --- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/DataSetDeliverer.java +++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/DataSetDeliverer.java @@ -113,12 +113,14 @@ public class DataSetDeliverer extends AbstractEntityDeliverer<DataSet> List<ContentCopy> contentCopies = linkedData.getContentCopies(); for (ContentCopy contentCopy : contentCopies) { + writer.writeStartElement("x:contentCopy"); addAttribute(writer, "externalCode", contentCopy.getExternalCode()); addAttribute(writer, "externalDMS", contentCopy.getExternalDms(), edms -> edms.getCode()); addAttribute(writer, "gitCommitHash", contentCopy.getGitCommitHash()); addAttribute(writer, "gitRepositoryId", contentCopy.getGitRepositoryId()); addAttribute(writer, "id", contentCopy.getId(), id -> id.getPermId()); addAttribute(writer, "path", contentCopy.getPath()); + writer.writeEndElement(); } addFileNodes(writer, code, context.getContentProvider().asContent(code).getRootNode()); writer.writeEndElement(); diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/DataSourceRequestHandler.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/DataSourceRequestHandler.java index 03ee078bdc9..c16e21089d3 100644 --- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/DataSourceRequestHandler.java +++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/DataSourceRequestHandler.java @@ -18,7 +18,6 @@ package ch.ethz.sis.openbis.generic.server.dss.plugins.sync.datasource; import java.io.File; import java.util.Arrays; -import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; @@ -27,7 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; -import java.util.TreeSet; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -35,25 +34,14 @@ import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; -import org.apache.log4j.Logger; - -import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSet; import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.Space; import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.fetchoptions.SpaceFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.search.SpaceSearchCriteria; -import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.common.ServiceFinderUtils; import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel; -import ch.systemsx.cisd.common.logging.LogCategory; -import ch.systemsx.cisd.common.logging.LogFactory; import ch.systemsx.cisd.common.properties.PropertyUtils; import ch.systemsx.cisd.openbis.dss.generic.server.oaipmh.IRequestHandler; import ch.systemsx.cisd.openbis.dss.generic.shared.DataSourceQueryService; -import ch.systemsx.cisd.openbis.dss.generic.shared.IHierarchicalContentProvider; import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProvider; -import ch.systemsx.cisd.openbis.generic.server.jython.api.v1.IMasterDataRegistrationTransaction; -import ch.systemsx.cisd.openbis.generic.server.jython.api.v1.impl.EncapsulatedCommonServer; -import ch.systemsx.cisd.openbis.generic.server.jython.api.v1.impl.MasterDataRegistrationService; import ch.systemsx.cisd.openbis.generic.shared.dto.SessionContextDTO; /** @@ -61,57 +49,125 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.SessionContextDTO; */ public class DataSourceRequestHandler implements IRequestHandler { - private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, DataSourceRequestHandler.class); + private enum Capability + { + ABOUT("about", "description", null, false), + CAPABILITY_LIST("capabilitylist", ABOUT, false), + RESOURCE_LIST("resourcelist", CAPABILITY_LIST, true) + { + @Override + void writeUrls(XMLStreamWriter writer, DeliveryContext context, IDeliverer deliverer, + Map<String, List<String>> parameterMap, String sessionToken, Date requestTimestamp) throws XMLStreamException + { + SpaceSearchCriteria searchCriteria = new SpaceSearchCriteria(); + SpaceFetchOptions fetchOptions = new SpaceFetchOptions(); + List<Space> spaces = context.getV3api().searchSpaces(sessionToken, searchCriteria, fetchOptions).getObjects(); + List<String> spaceCodes = spaces.stream().map(Space::getCode).collect(Collectors.toList()); + Set<String> requestedSpaces = DataSourceUtils.getRequestedAndAllowedSubSet(spaceCodes, + parameterMap.get("white_list"), parameterMap.get("black_list")); + deliverer.deliverEntities(writer, sessionToken, requestedSpaces, requestTimestamp); + } + }; - private File tempDir; + private String name; - private String serverUrl; + private Capability up; - private String downloadUrl; + private Capability down; - private String servletPath; + private String capabilityAttribute; - private DataSourceQueryService queryService; + private boolean withAt; - private IApplicationServerApi v3api; + static + { + ABOUT.down = CAPABILITY_LIST; + CAPABILITY_LIST.down = RESOURCE_LIST; + } - private IHierarchicalContentProvider contentProvider; + private Capability(String name, Capability up, boolean withAt) + { + this(name, name, up, withAt); + } - private AbstractEntityDeliverer<DataSet> dataSetDeliverer; + private Capability(String name, String capabilityAttribute, Capability up, boolean withAt) + { + this.name = name; + this.capabilityAttribute = capabilityAttribute; + this.up = up; + this.withAt = withAt; + } - private ExperimentDeliverer experimentDeliverer; + boolean matchVerb(Set<String> verbs) + { + return verbs.contains(asVerb()); + } - private MaterialDeliverer materialDeliverer; + String asVerb() + { + return name + ".xml"; + } - private SampleDeliverer sampleDeliverer; + void write(XMLStreamWriter writer, DeliveryContext context, IDeliverer deliverer, + Map<String, List<String>> parameterMap, String sessionToken, Date requestTimestamp) throws XMLStreamException + { + writer.writeStartElement("rs:ln"); + String verb = up == null ? asVerb() : up.asVerb(); + writer.writeAttribute("href", createDownloadUrl(context, verb)); + writer.writeAttribute("rel", up == null ? "describedby" : "up"); + writer.writeEndElement(); + writer.writeStartElement("rs:md"); + if (withAt) + { + writer.writeAttribute("at", DataSourceUtils.convertToW3CDate(requestTimestamp)); + } + writer.writeAttribute("capability", capabilityAttribute); + writer.writeEndElement(); + writeUrls(writer, context, deliverer, parameterMap, sessionToken, requestTimestamp); + } + + void writeUrls(XMLStreamWriter writer, DeliveryContext context, IDeliverer deliverer, + Map<String, List<String>> parameterMap, String sessionToken, Date requestTimestamp) throws XMLStreamException + { + writer.writeStartElement("url"); + writer.writeStartElement("loc"); + writer.writeCharacters(createDownloadUrl(context, down.asVerb())); + writer.writeEndElement(); + writer.writeStartElement("rs:md"); + writer.writeAttribute("capability", down.name); + writer.writeEndElement(); + } - private ProjectDeliverer projectDeliverer; + private String createDownloadUrl(DeliveryContext context, String verb) + { + return context.getDownloadUrl() + context.getServletPath() + "/?verb=" + verb; + } - private MasterDataDeliverer masterDataDeliverer; + } + + private DataSourceQueryService queryService; + + private IDeliverer deliverer; + + private DeliveryContext deliveryContext; @Override public void init(Properties properties) { - DeliveryContext deliveryContext = new DeliveryContext(); - servletPath = new File(PropertyUtils.getMandatoryProperty(properties, "path")).getParent(); - deliveryContext.setServletPath(servletPath); - tempDir = new File(PropertyUtils.getMandatoryProperty(properties, "temp-dir")); - serverUrl = PropertyUtils.getMandatoryProperty(properties, "server-url"); - deliveryContext.setServerUrl(serverUrl); - downloadUrl = PropertyUtils.getMandatoryProperty(properties, "download-url"); - deliveryContext.setDownloadUrl(downloadUrl); queryService = new DataSourceQueryService(); - v3api = ServiceProvider.getV3ApplicationService(); - deliveryContext.setV3api(v3api); - contentProvider = ServiceProvider.getHierarchicalContentProvider(); - deliveryContext.setContentProvider(contentProvider); - dataSetDeliverer = new DataSetDeliverer(deliveryContext); - experimentDeliverer = new ExperimentDeliverer(deliveryContext); - materialDeliverer = new MaterialDeliverer(deliveryContext); - sampleDeliverer = new SampleDeliverer(deliveryContext); - projectDeliverer = new ProjectDeliverer(deliveryContext); - String openBisServerUrl = ServiceProvider.getConfigProvider().getOpenBisServerUrl(); - masterDataDeliverer = new MasterDataDeliverer(deliveryContext); + deliveryContext = new DeliveryContext(); + deliveryContext.setServletPath(new File(PropertyUtils.getMandatoryProperty(properties, "path")).getParent()); + deliveryContext.setServerUrl(PropertyUtils.getMandatoryProperty(properties, "server-url")); + deliveryContext.setDownloadUrl(PropertyUtils.getMandatoryProperty(properties, "download-url")); + deliveryContext.setV3api(ServiceProvider.getV3ApplicationService()); + deliveryContext.setContentProvider(ServiceProvider.getHierarchicalContentProvider()); + Deliverers deliverers = new Deliverers(); + deliverers.addDeliverer(new DataSetDeliverer(deliveryContext)); + deliverers.addDeliverer(new MaterialDeliverer(deliveryContext)); + deliverers.addDeliverer(new SampleDeliverer(deliveryContext)); + deliverers.addDeliverer(new ProjectDeliverer(deliveryContext)); + deliverers.addDeliverer(new MasterDataDeliverer(deliveryContext)); + deliverer = deliverers; } @Override @@ -119,7 +175,7 @@ public class DataSourceRequestHandler implements IRequestHandler { try { - Map<String, Set<String>> parameterMap = getParameterMap(request); + Map<String, List<String>> parameterMap = getParameterMap(request); XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance(); XMLStreamWriter writer = xmlOutputFactory.createXMLStreamWriter(response.getWriter()); writer.writeStartDocument(); @@ -132,11 +188,10 @@ public class DataSourceRequestHandler implements IRequestHandler writer.writeAttribute("xsi:schemaLocation", "https://sis.id.ethz.ch/software/#openbis/xdterms/ ./xml/xdterms.xsd https://sis.id.ethz.ch/software/#openbis/xmdterms/"); String sessionToken = session.getSessionToken(); - Set<String> verbs = parameterMap.get("verb"); - if (verbs.contains("resourcelist.xml")) - { - deliverResourceList(parameterMap, sessionToken, writer); - } + Date requestTimestamp = getRequestTimestamp(); + Set<String> verbs = new HashSet<>(parameterMap.get("verb")); + Capability capability = findMatchingCapability(verbs); + capability.write(writer, deliveryContext, deliverer, parameterMap, sessionToken, requestTimestamp); writer.writeEndElement(); writer.writeEndDocument(); } catch (Exception e) @@ -145,41 +200,22 @@ public class DataSourceRequestHandler implements IRequestHandler } } - private void deliverResourceList(Map<String, Set<String>> parameterMap, String sessionToken, XMLStreamWriter writer) throws XMLStreamException + private Capability findMatchingCapability(Set<String> verbs) { - - writer.writeStartElement("rs:ln"); - writer.writeAttribute("href", downloadUrl + servletPath + "/?verb=capabilitylist.xml"); - writer.writeAttribute("rel", "up"); - writer.writeEndElement(); - writer.writeStartElement("rs:md"); - Date requestTimestamp = getRequestTimestamp(); - writer.writeAttribute("at", DataSourceUtils.convertToW3CDate(requestTimestamp)); - writer.writeAttribute("capability", "resourceList"); - writer.writeEndElement(); - Set<String> ignoredSpaces = parameterMap.get("black_list"); - if (ignoredSpaces == null) - { - ignoredSpaces = Collections.emptySet(); - } - Set<String> requestedSpaces = new TreeSet<>(); - List<Space> spaces = v3api.searchSpaces(sessionToken, new SpaceSearchCriteria(), new SpaceFetchOptions()).getObjects(); - for (Space space : spaces) + if (verbs != null) { - if (ignoredSpaces.contains(space.getCode()) == false) + for (Capability capability : Capability.values()) { - requestedSpaces.add(space.getCode()); + if (capability.matchVerb(verbs)) + { + return capability; + } } } - dataSetDeliverer.deliverEntities(writer, sessionToken, requestedSpaces, requestTimestamp); - experimentDeliverer.deliverEntities(writer, sessionToken, requestedSpaces, requestTimestamp); - masterDataDeliverer.deliverEntities(writer, sessionToken, requestedSpaces, requestTimestamp);; - materialDeliverer.deliverEntities(writer, sessionToken, requestedSpaces, requestTimestamp); - projectDeliverer.deliverEntities(writer, sessionToken, requestedSpaces, requestTimestamp); - sampleDeliverer.deliverEntities(writer, sessionToken, requestedSpaces, requestTimestamp); + return Capability.ABOUT; } - protected Date getRequestTimestamp() + private Date getRequestTimestamp() { Date requestTimestamp = new Date(); String query = "select xact_start FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_start ASC LIMIT 1"; @@ -190,14 +226,14 @@ public class DataSourceRequestHandler implements IRequestHandler return requestTimestamp; } - private Map<String, Set<String>> getParameterMap(HttpServletRequest request) + private Map<String, List<String>> getParameterMap(HttpServletRequest request) { Enumeration<String> enumeration = request.getParameterNames(); - Map<String, Set<String>> parameterMap = new HashMap<>(); + Map<String, List<String>> parameterMap = new HashMap<>(); while (enumeration.hasMoreElements()) { String parameter = enumeration.nextElement(); - parameterMap.put(parameter, new HashSet<>(Arrays.asList(request.getParameterValues(parameter)))); + parameterMap.put(parameter, Arrays.asList(request.getParameterValues(parameter))); } return parameterMap; } diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/DataSourceUtils.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/DataSourceUtils.java index 6390ce5dbbb..e41a5891930 100644 --- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/DataSourceUtils.java +++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/DataSourceUtils.java @@ -17,13 +17,18 @@ package ch.ethz.sis.openbis.generic.server.dss.plugins.sync.datasource; import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; import java.util.Date; +import java.util.LinkedHashSet; +import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.TimeZone; +import java.util.regex.Pattern; /** * @author Franz-Josef Elmer - * */ class DataSourceUtils { @@ -35,4 +40,57 @@ class DataSourceUtils return format.format(date) + "Z"; } + /** + * Return the sub set from the full where non of the regexs from the specified black lists matches and at least + * one regex of the white lists matches. An empty or null whiteLists means that only the check against black lists + * are done. + */ + static Set<String> getRequestedAndAllowedSubSet(Collection<String> fullSet, List<String> whiteLists, List<String> blackLists) + { + List<Pattern> allowedPatterns = getRegexs(whiteLists); + List<Pattern> disallowedPatterns = getRegexs(blackLists); + Set<String> subSet = new LinkedHashSet<>(); + for (String item : fullSet) + { + if (matchesARegex(disallowedPatterns, item)) + { + continue; + } + if (allowedPatterns.isEmpty() || matchesARegex(allowedPatterns, item)) + { + subSet.add(item); + } + } + return subSet; + + } + + private static boolean matchesARegex(List<Pattern> patterns, String item) + { + for (Pattern pattern : patterns) + { + if (pattern.matcher(item).matches()) + { + return true; + } + } + return false; + } + + private static List<Pattern> getRegexs(List<String> lists) + { + List<Pattern> regexs = new ArrayList<>(); + if (lists != null) + { + for (String commaSeparatedList : lists) + { + String[] splitted = commaSeparatedList.split(","); + for (String string : splitted) + { + regexs.add(Pattern.compile(string.trim().toUpperCase())); + } + } + } + return regexs; + } } diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/Deliverers.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/Deliverers.java new file mode 100644 index 00000000000..e521af442f4 --- /dev/null +++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/Deliverers.java @@ -0,0 +1,48 @@ +/* + * Copyright 2019 ETH Zuerich, SIS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.ethz.sis.openbis.generic.server.dss.plugins.sync.datasource; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +/** + * @author Franz-Josef Elmer + */ +public class Deliverers implements IDeliverer +{ + private List<IDeliverer> deliverers = new ArrayList<>(); + + public void addDeliverer(IDeliverer deliverer) + { + deliverers.add(deliverer); + } + + @Override + public void deliverEntities(XMLStreamWriter writer, String sessionToken, Set<String> spaces, Date requestTimestamp) throws XMLStreamException + { + for (IDeliverer deliverer : deliverers) + { + deliverer.deliverEntities(writer, sessionToken, spaces, requestTimestamp); + } + } + +} diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/IDeliverer.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/IDeliverer.java new file mode 100644 index 00000000000..0939862791a --- /dev/null +++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/IDeliverer.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 ETH Zuerich, SIS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.ethz.sis.openbis.generic.server.dss.plugins.sync.datasource; + +import java.util.Date; +import java.util.Set; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +/** + * @author Franz-Josef Elmer + * + * @param <T> + */ +interface IDeliverer +{ + + void deliverEntities(XMLStreamWriter writer, String sessionToken, Set<String> spaces, Date requestTimestamp) throws XMLStreamException; + +} \ No newline at end of file diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/MasterDataDeliverer.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/MasterDataDeliverer.java index c3ffae1048f..69a2be5513b 100644 --- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/MasterDataDeliverer.java +++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/datasource/MasterDataDeliverer.java @@ -89,7 +89,7 @@ public class MasterDataDeliverer extends AbstractEntityDeliverer<Object> } @Override - void deliverEntities(XMLStreamWriter writer, String sessionToken, Set<String> spaces, Date requestTimestamp) throws XMLStreamException + public void deliverEntities(XMLStreamWriter writer, String sessionToken, Set<String> spaces, Date requestTimestamp) throws XMLStreamException { startUrlElement(writer); addLocation(writer, "MASTER_DATA", "MASTER_DATA"); -- GitLab