From d8ee61beeca81c0c88ca5f7d2922372cf6ad08af Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Wed, 15 Jan 2025 16:50:49 +0100 Subject: [PATCH 1/2] JCR-5123 Provide new WebDav client API based on Java HTTP client WIP --- .../apache/jackrabbit/webdav/MultiStatus.java | 15 +++ .../webdav/client/AbstractWebdavMethod.java | 100 ++++++++++++++++++ .../jackrabbit/webdav/client/BindMethod.java | 42 ++++++++ .../jackrabbit/webdav/client/CopyMethod.java | 55 ++++++++++ .../webdav/client/DavBodyHandlers.java | 75 +++++++++++++ .../webdav/client/DavBodyPublishers.java | 44 ++++++++ .../webdav/client/DavResponseException.java | 23 ++++ .../jackrabbit/webdav/client/LockMethod.java | 94 ++++++++++++++++ .../MultiStatusDavResponseException.java | 19 ++++ .../webdav/client/methods/HttpLock.java | 2 +- .../webdav/client/package-info.java | 31 ++++++ .../apache/jackrabbit/webdav/xml/DomUtil.java | 52 +++++---- 12 files changed, 533 insertions(+), 19 deletions(-) create mode 100644 jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/AbstractWebdavMethod.java create mode 100644 jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/BindMethod.java create mode 100644 jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/CopyMethod.java create mode 100644 jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavBodyHandlers.java create mode 100644 jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavBodyPublishers.java create mode 100644 jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavResponseException.java create mode 100644 jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/LockMethod.java create mode 100644 jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/MultiStatusDavResponseException.java create mode 100644 jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/package-info.java diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/MultiStatus.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/MultiStatus.java index 79536b628aa..81a7983e280 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/MultiStatus.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/MultiStatus.java @@ -128,6 +128,21 @@ public synchronized MultiStatusResponse[] getResponses() { return responses.values().toArray(new MultiStatusResponse[responses.size()]); } + /** + * + * @return {@code true} if at least one status code in any of the responses indicates an error, {@code false} otherwise. + * @since 2.24.0 + */ + public boolean containsErrorStatus() { + for (MultiStatusResponse response : getResponses()) { + for (Status status : response.getStatus()) { + if (status.getStatusCode() >= DavServletResponse.SC_BAD_REQUEST) { + return true; + } + } + } + return false; + } /** * Set the response description. * diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/AbstractWebdavMethod.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/AbstractWebdavMethod.java new file mode 100644 index 00000000000..e5a02e244ae --- /dev/null +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/AbstractWebdavMethod.java @@ -0,0 +1,100 @@ +package org.apache.jackrabbit.webdav.client; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.Builder; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodySubscribers; +import java.net.http.HttpResponse.ResponseInfo; +import java.util.Optional; +import java.util.function.Function; + +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.jackrabbit.webdav.DavServletResponse; +import org.apache.jackrabbit.webdav.MultiStatus; +import org.apache.jackrabbit.webdav.client.methods.BaseDavRequest; +import org.apache.jackrabbit.webdav.xml.DomUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +/** + * Encapsulates functionality both to create HTTP requests as well as to parse responses of a specific WebDAV method. + */ +public abstract class AbstractWebdavMethod { + + protected HttpRequest.Builder newRequestBuilder(URI uri) throws IOException { + Builder builder = HttpRequest.newBuilder(uri); + return builder; + } + + /** + * Parses the response in case of non-success status code and throws an exception. + * Also potentially parses multi status response bodies and wraps them in a {@link MultiStatusDavResponseException}. + * @throws MultiStatusDavResponseException in case of a multi status response + * @throws DavResponseException in case of a non-success status code + */ + protected HttpResponse.BodyHandler newMultiStatusAwareBodyHandler() { + return newMultiStatusAwareBodyHandler(responseInfo -> {}); + } + + /** + * Parses the response in case of non-success status code and throws an exception. + * Also potentially parses multi status response bodies and wraps them in a {@link MultiStatusDavResponseException}. + * @throws MultiStatusDavResponseException in case of a multi status response + * @throws DavResponseException in case of a non-success status code + */ + protected HttpResponse.BodyHandler newMultiStatusAwareBodyHandler(Function responseInfoConsumer) { + return (responseInfo) -> { + if (succeeded(responseInfo.statusCode())) { + T response = responseInfoConsumer.apply(null); + return BodySubscribers.replacing(response); + } else if (responseInfo.statusCode() == DavServletResponse.SC_MULTI_STATUS) { + // TODO: evaluate multi status response body also for successful status codes? + return BodySubscribers.ofByteArrayConsumer(AbstractWebdavMethod::evaluateMultiStatusResponseBody); + } else { + throw new DavResponseException(responseInfo.statusCode(), "Unexpected status code: "); + } + }; + } + + public abstract HttpResponse.BodyHandler newBodyResponseHandler(); + + private static void evaluateMultiStatusResponseBody(Optional responseBody) { + if (!responseBody.isPresent()) { + return; + } + // parse as document + Document dom; + try { + dom = DomUtil.parseDocument(responseBody.get()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (SAXException e) { + // TODO: IO exception? + throw new IllegalStateException("Unexpected XML parsing error", e); + } catch (ParserConfigurationException e) { + throw new IllegalStateException("Unexpected XML parsing parse configuration error", e); + } + MultiStatus multiStatus = MultiStatus.createFromXml(dom.getDocumentElement()); + if (multiStatus.containsErrorStatus()) { + throw new MultiStatusDavResponseException(multiStatus); + } + } + + /** + * Determines whether the given response status code indicates a successful request. + * The default implementation treats all + * 2xx status codes (RFC 7231, Section 6.3) as success. + * Implementations can further restrict the accepted range of status codes. + * @param statusCode + * @return {@code true} in case the status code indicates a successful request, {@code false} otherwise + */ + protected boolean succeeded(int statusCode) { + return statusCode >= 200 && statusCode <= 300; + } +} diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/BindMethod.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/BindMethod.java new file mode 100644 index 00000000000..f57d6892b5e --- /dev/null +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/BindMethod.java @@ -0,0 +1,42 @@ +package org.apache.jackrabbit.webdav.client; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpRequest.Builder; +import java.net.http.HttpResponse.BodyHandler; + +import org.apache.jackrabbit.webdav.DavMethods; +import org.apache.jackrabbit.webdav.DavServletResponse; +import org.apache.jackrabbit.webdav.bind.BindInfo; + +/** + * Represents an HTTP BIND method. + * + * @see RFC 5842, Section 4 + */ +public class BindMethod extends AbstractWebdavMethod { + + private final BindInfo info; + + BindMethod(BindInfo info) { + this.info = info; + } + + @Override + public Builder newRequestBuilder(URI uri) throws IOException { + Builder builder = super.newRequestBuilder(uri); + builder.method(DavMethods.METHOD_BIND, DavBodyPublishers.ofXmlSerializable(info)); + return builder; + } + + @Override + public BodyHandler newBodyResponseHandler() { + return newMultiStatusAwareBodyHandler(); + } + + @Override + protected boolean succeeded(int statusCode) { + return statusCode == DavServletResponse.SC_OK || statusCode == DavServletResponse.SC_CREATED; + } + +} diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/CopyMethod.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/CopyMethod.java new file mode 100644 index 00000000000..0a8e5793ee6 --- /dev/null +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/CopyMethod.java @@ -0,0 +1,55 @@ +package org.apache.jackrabbit.webdav.client; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.Builder; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandler; + +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.DavMethods; +import org.apache.jackrabbit.webdav.DavServletResponse; + +/** + * Represents an HTTP COPY method. + * + * @see RFC 4918, Section 9.8 + */ +public class CopyMethod extends AbstractWebdavMethod { + + private final URI dest; + private final boolean overwrite; + private final boolean shallow; + + CopyMethod(URI dest, boolean overwrite, boolean shallow) { + this.dest = dest; + this.overwrite = overwrite; + this.shallow = shallow; + } + + @Override + public Builder newRequestBuilder(URI uri) throws IOException { + Builder builder = super.newRequestBuilder(uri); + builder.method(DavMethods.METHOD_COPY, HttpRequest.BodyPublishers.noBody()); + builder.setHeader(DavConstants.HEADER_DESTINATION, dest.toASCIIString()); + if (!overwrite) { + builder.setHeader(DavConstants.HEADER_OVERWRITE, "F"); + } + if (shallow) { + builder.setHeader("Depth", "0"); + } + return builder; + } + + @Override + public BodyHandler newBodyResponseHandler() { + return newMultiStatusAwareBodyHandler(); + } + + @Override + protected boolean succeeded(int statusCode) { + return statusCode == DavServletResponse.SC_CREATED || statusCode == DavServletResponse.SC_NO_CONTENT; + } + +} diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavBodyHandlers.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavBodyHandlers.java new file mode 100644 index 00000000000..1792346369f --- /dev/null +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavBodyHandlers.java @@ -0,0 +1,75 @@ +package org.apache.jackrabbit.webdav.client; + +import java.io.IOException; +import java.net.http.HttpHeaders; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandler; +import java.net.http.HttpResponse.BodySubscriber; +import java.net.http.HttpResponse.BodySubscribers; +import java.net.http.HttpResponse.ResponseInfo; +import java.nio.ByteBuffer; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Flow.Subscriber; +import java.util.concurrent.Flow.Subscription; + +import org.apache.http.StatusLine; +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.MultiStatus; +import org.w3c.dom.Element; + +/** + * Factory methods for HTTP response body handlers. Body handlers are used to process the body of an HTTP response. + */ +public class DavBodyHandlers { + + + /** + * Returns a {@code BodyHandler} that returns a + * {@link BodySubscriber BodySubscriber}{@code } obtained from + * {@link BodySubscribers#ofFile(Path, OpenOption...) + * BodySubscribers.ofFile(Path,OpenOption...)}. + * + *

When the {@code HttpResponse} object is returned, the body has + * been completely written to the file, and {@link #body()} returns a + * reference to its {@link Path}. + * + *

Security manager permission checks are performed in this factory + * method, when the {@code BodyHandler} is created. Care must be taken + * that the {@code BodyHandler} is not shared with untrusted code. + * + * @param file the file to store the body in + * @param openOptions any options to use when opening/creating the file + * @return a response body handler + * @throws IllegalArgumentException if an invalid set of open options + * are specified + * @throws SecurityException If a security manager has been installed + * and it denies {@link SecurityManager#checkWrite(String) + * write access} to the file. + */ + public static BodyHandler ofLockToken() { + return (responseInfo) -> BodySubscribers.replacing(getLockToken(responseInfo.headers())); + } + + + static String getLockToken(HttpHeaders headers) { + List ltHeader = headers.allValues(DavConstants.HEADER_LOCK_TOKEN); + if (ltHeader == null || ltHeader.length == 0) { + return null; + } else if (ltHeader.length != 1) { + LOG.debug("Multiple 'Lock-Token' header fields in response for " + getURI() + ": " + Arrays.asList(ltHeader)); + return null; + } else { + String v = ltHeader[0].getValue().trim(); + if (!v.startsWith("<") || !v.endsWith(">")) { + LOG.debug("Invalid 'Lock-Token' header field in response for " + getURI() + ": " + Arrays.asList(ltHeader)); + return null; + } else { + return v.substring(1, v.length() - 1); + } + } + } +} diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavBodyPublishers.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavBodyPublishers.java new file mode 100644 index 00000000000..677df271ae8 --- /dev/null +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavBodyPublishers.java @@ -0,0 +1,44 @@ +package org.apache.jackrabbit.webdav.client; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.http.HttpRequest; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.apache.jackrabbit.webdav.xml.DomUtil; +import org.apache.jackrabbit.webdav.xml.XmlSerializable; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +/** + * Factory methods for HTTP request body publishers. Body publishers are used to provide the body of an HTTP request. + */ +public class DavBodyPublishers { + + private DavBodyPublishers() { + + } + + public static HttpRequest.BodyPublisher ofXmlSerializable(XmlSerializable xmlSerializable) throws IOException { + try { + Document doc = DomUtil.createDocument(); + doc.appendChild(xmlSerializable.toXml(doc)); + return ofDocument(doc); + } catch (ParserConfigurationException ex) { + throw new IOException(ex); + } + } + + private static HttpRequest.BodyPublisher ofDocument(Document doc) throws IOException { + try { + ByteArrayOutputStream xml = new ByteArrayOutputStream(); + DomUtil.transformDocument(doc, xml); + return HttpRequest.BodyPublishers.ofByteArray(xml.toByteArray()); + } catch (TransformerException|SAXException ex) { + throw new IOException(ex); + } + } + +} diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavResponseException.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavResponseException.java new file mode 100644 index 00000000000..4c05dd7fa89 --- /dev/null +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavResponseException.java @@ -0,0 +1,23 @@ +package org.apache.jackrabbit.webdav.client; + +/** Similar to DavException, but unchecked. */ +public class DavResponseException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private final int statusCode; + + public DavResponseException(int statusCode, String message) { + super(message); + this.statusCode = statusCode; + } + + public DavResponseException(int statusCode, String message, Throwable cause) { + super(message, cause); + this.statusCode = statusCode; + } + + public int getStatusCode() { + return statusCode; + } +} diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/LockMethod.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/LockMethod.java new file mode 100644 index 00000000000..c5033efe9fe --- /dev/null +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/LockMethod.java @@ -0,0 +1,94 @@ +package org.apache.jackrabbit.webdav.client; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpHeaders; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.Builder; +import java.net.http.HttpResponse.BodyHandler; +import java.net.http.HttpResponse.ResponseInfo; +import java.util.Arrays; +import java.util.List; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.DavMethods; +import org.apache.jackrabbit.webdav.DavServletResponse; +import org.apache.jackrabbit.webdav.bind.BindInfo; +import org.apache.jackrabbit.webdav.client.methods.HttpLock; +import org.apache.jackrabbit.webdav.client.methods.XmlEntity; +import org.apache.jackrabbit.webdav.header.IfHeader; +import org.apache.jackrabbit.webdav.header.TimeoutHeader; +import org.apache.jackrabbit.webdav.lock.LockInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents an HTTP LOCK method. + * + * @see RFC 4918, Section 9.10 + */ +public class LockMethod extends AbstractWebdavMethod { + + private static final Logger LOG = LoggerFactory.getLogger(LockMethod.class); + + private final LockInfo info; + private final boolean isRefresh; + + LockMethod(LockInfo info) { + this.info = info; + this.isRefresh = false; + } + + // TODO: separate into two classes + LockMethod(long timeout, Collection lockTokens) { + TimeoutHeader th = new TimeoutHeader(timeout); + super.setHeader(th.getHeaderName(), th.getHeaderValue()); + IfHeader ifh = new IfHeader(lockTokens); + super.setHeader(ifh.getHeaderName(), ifh.getHeaderValue()); + isRefresh = true; + } + + @Override + public Builder newRequestBuilder(URI uri) throws IOException { + Builder builder = super.newRequestBuilder(uri); + if (isRefresh) { + builder.method(DavMethods.METHOD_LOCK, HttpRequest.BodyPublishers.noBody()); + TimeoutHeader th = new TimeoutHeader(timeout); + builder.setHeader(th.getHeaderName(), th.getHeaderValue()); + IfHeader ifh = new IfHeader(lockTokens); + builder.setHeader(ifh.getHeaderName(), ifh.getHeaderValue()); + } else { + builder.method(DavMethods.METHOD_LOCK, DavBodyPublishers.ofXmlSerializable(info)); + } + return builder; + } + + @Override + public BodyHandler newBodyResponseHandler() { + return newMultiStatusAwareBodyHandler(this::getLockToken); + } + + private String getLockToken(HttpHeaders headers) { + List ltHeader = headers.allValues(DavConstants.HEADER_LOCK_TOKEN); + if (ltHeader.size() != 1) { + LOG.debug("Multiple 'Lock-Token' header fields in response for " + response. + ": " + Arrays.asList(ltHeader)); + return null; + } else { + String v = ltHeader.get(0).trim(); + if (!v.startsWith("<") || !v.endsWith(">")) { + LOG.debug("Invalid 'Lock-Token' header field in response for " + getURI() + ": " + Arrays.asList(ltHeader)); + return null; + } else { + return v.substring(1, v.length() - 1); + } + } + } + + @Override + protected boolean succeeded(int statusCode) { + return statusCode == DavServletResponse.SC_OK || statusCode == DavServletResponse.SC_CREATED; + } + +} diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/MultiStatusDavResponseException.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/MultiStatusDavResponseException.java new file mode 100644 index 00000000000..04f59dd743f --- /dev/null +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/MultiStatusDavResponseException.java @@ -0,0 +1,19 @@ +package org.apache.jackrabbit.webdav.client; + +import org.apache.jackrabbit.webdav.MultiStatus; + +/** Exception thrown when a multi-status response indicating a failure is received. */ +public class MultiStatusDavResponseException extends DavResponseException { + + private static final long serialVersionUID = 1L; + + private final MultiStatus multiStatus; + public MultiStatusDavResponseException(MultiStatus multiStatus) { + super(207, "Multi-Status response" + multiStatus.toString()); + this.multiStatus = multiStatus; + } + + public MultiStatus getMultiStatus() { + return multiStatus; + } +} diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLock.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLock.java index 171776d17c5..97a31f235ea 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLock.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLock.java @@ -40,7 +40,7 @@ */ public class HttpLock extends BaseDavRequest { - private static final Logger LOG = LoggerFactory.getLogger(HttpLock.class); + private final boolean isRefresh; diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/package-info.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/package-info.java new file mode 100644 index 00000000000..5fa09379e1f --- /dev/null +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/package-info.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * Provides classes for use with the Java HttpClient, supporting WebDAV + * request methods. + *

+ * The classes in this package are used to create and execute WebDAV requests like this + * {@code + * TODO + * + * } + * @see Introduction to the Java HTTP Client + */ +@org.osgi.annotation.versioning.Version("1.0.0") +package org.apache.jackrabbit.webdav.client; diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java index 86d19133a6a..9bc1464e304 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java @@ -16,18 +16,12 @@ */ package org.apache.jackrabbit.webdav.xml; -import org.apache.jackrabbit.webdav.DavConstants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.Attr; -import org.w3c.dom.CharacterData; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.w3c.dom.Text; -import org.w3c.dom.NamedNodeMap; -import org.xml.sax.SAXException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; @@ -39,12 +33,18 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Writer; -import java.util.ArrayList; -import java.util.List; +import org.apache.jackrabbit.webdav.DavConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Attr; +import org.w3c.dom.CharacterData; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; +import org.xml.sax.SAXException; /** * DomUtil provides some common utility methods related to w3c-DOM. @@ -104,6 +104,22 @@ public static Document parseDocument(InputStream stream) return docBuilder.parse(stream); } + /** + * Parses the given bytes and returns the resulting DOM document. + * + * @param data the bytes containing the XML data + * @return parsed DOM document + * @throws ParserConfigurationException if the document can not be created + * @throws SAXException if the document can not be parsed + * @throws IOException if the input stream can not be read + */ + public static Document parseDocument(byte[] data) + throws ParserConfigurationException, SAXException, IOException { + try (InputStream stream = new java.io.ByteArrayInputStream(data)) { + return parseDocument(stream); + } + } + /** * Returns the value of the named attribute of the current element. * From ed86760213233e7f554e8714361d912130270c8f Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Mon, 9 Feb 2026 16:38:04 +0100 Subject: [PATCH 2/2] make code compilable --- .../webdav/client/AbstractWebdavMethod.java | 20 +++----- .../webdav/client/DavBodyHandlers.java | 47 +++++++------------ .../jackrabbit/webdav/client/LockMethod.java | 39 ++++----------- .../webdav/client/methods/HttpLock.java | 2 +- .../webdav/client/package-info.java | 2 +- .../jackrabbit/webdav/package-info.java | 2 +- .../jackrabbit/webdav/xml/package-info.java | 2 +- 7 files changed, 34 insertions(+), 80 deletions(-) diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/AbstractWebdavMethod.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/AbstractWebdavMethod.java index e5a02e244ae..1fea3ab3a4d 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/AbstractWebdavMethod.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/AbstractWebdavMethod.java @@ -6,6 +6,7 @@ import java.net.http.HttpRequest; import java.net.http.HttpRequest.Builder; import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodySubscriber; import java.net.http.HttpResponse.BodySubscribers; import java.net.http.HttpResponse.ResponseInfo; import java.util.Optional; @@ -38,24 +39,15 @@ protected HttpRequest.Builder newRequestBuilder(URI uri) throws IOException { * @throws MultiStatusDavResponseException in case of a multi status response * @throws DavResponseException in case of a non-success status code */ - protected HttpResponse.BodyHandler newMultiStatusAwareBodyHandler() { - return newMultiStatusAwareBodyHandler(responseInfo -> {}); - } - - /** - * Parses the response in case of non-success status code and throws an exception. - * Also potentially parses multi status response bodies and wraps them in a {@link MultiStatusDavResponseException}. - * @throws MultiStatusDavResponseException in case of a multi status response - * @throws DavResponseException in case of a non-success status code - */ - protected HttpResponse.BodyHandler newMultiStatusAwareBodyHandler(Function responseInfoConsumer) { + // TODO: pass subscriber (no access to response headers)? + protected HttpResponse.BodyHandler newMultiStatusAwareBodyHandler() { return (responseInfo) -> { if (succeeded(responseInfo.statusCode())) { - T response = responseInfoConsumer.apply(null); - return BodySubscribers.replacing(response); + // TODO: method specific evaluation of both headers and body + return (BodySubscriber) BodySubscribers.discarding(); } else if (responseInfo.statusCode() == DavServletResponse.SC_MULTI_STATUS) { // TODO: evaluate multi status response body also for successful status codes? - return BodySubscribers.ofByteArrayConsumer(AbstractWebdavMethod::evaluateMultiStatusResponseBody); + return (BodySubscriber) BodySubscribers.ofByteArrayConsumer(AbstractWebdavMethod::evaluateMultiStatusResponseBody); } else { throw new DavResponseException(responseInfo.statusCode(), "Unexpected status code: "); } diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavBodyHandlers.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavBodyHandlers.java index 1792346369f..6bb3fe75fec 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavBodyHandlers.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/DavBodyHandlers.java @@ -19,53 +19,38 @@ import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.MultiStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.w3c.dom.Element; /** * Factory methods for HTTP response body handlers. Body handlers are used to process the body of an HTTP response. + * @see {@link BodyHandler} */ public class DavBodyHandlers { - - - /** - * Returns a {@code BodyHandler} that returns a - * {@link BodySubscriber BodySubscriber}{@code } obtained from - * {@link BodySubscribers#ofFile(Path, OpenOption...) - * BodySubscribers.ofFile(Path,OpenOption...)}. - * - *

When the {@code HttpResponse} object is returned, the body has - * been completely written to the file, and {@link #body()} returns a - * reference to its {@link Path}. - * - *

Security manager permission checks are performed in this factory - * method, when the {@code BodyHandler} is created. Care must be taken - * that the {@code BodyHandler} is not shared with untrusted code. - * - * @param file the file to store the body in - * @param openOptions any options to use when opening/creating the file - * @return a response body handler - * @throws IllegalArgumentException if an invalid set of open options - * are specified - * @throws SecurityException If a security manager has been installed - * and it denies {@link SecurityManager#checkWrite(String) - * write access} to the file. - */ + + private static final Logger LOG = LoggerFactory.getLogger(DavBodyHandlers.class); + + private DavBodyHandlers() { + // prevent instantiation + } + public static BodyHandler ofLockToken() { return (responseInfo) -> BodySubscribers.replacing(getLockToken(responseInfo.headers())); } - + // TODO: exception handling static String getLockToken(HttpHeaders headers) { List ltHeader = headers.allValues(DavConstants.HEADER_LOCK_TOKEN); - if (ltHeader == null || ltHeader.length == 0) { + if (ltHeader.isEmpty()) { return null; - } else if (ltHeader.length != 1) { - LOG.debug("Multiple 'Lock-Token' header fields in response for " + getURI() + ": " + Arrays.asList(ltHeader)); + } else if (ltHeader.size() != 1) { + LOG.debug("Multiple 'Lock-Token' header fields in response {}", ltHeader); return null; } else { - String v = ltHeader[0].getValue().trim(); + String v = ltHeader.get(0).trim(); if (!v.startsWith("<") || !v.endsWith(">")) { - LOG.debug("Invalid 'Lock-Token' header field in response for " + getURI() + ": " + Arrays.asList(ltHeader)); + LOG.debug("Invalid 'Lock-Token' header field in response: {}", v); return null; } else { return v.substring(1, v.length() - 1); diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/LockMethod.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/LockMethod.java index c5033efe9fe..31d9cc4dacf 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/LockMethod.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/LockMethod.java @@ -8,6 +8,7 @@ import java.net.http.HttpResponse.BodyHandler; import java.net.http.HttpResponse.ResponseInfo; import java.util.Arrays; +import java.util.Collection; import java.util.List; import org.apache.http.Header; @@ -34,56 +35,32 @@ public class LockMethod extends AbstractWebdavMethod { private static final Logger LOG = LoggerFactory.getLogger(LockMethod.class); private final LockInfo info; - private final boolean isRefresh; - LockMethod(LockInfo info) { + public LockMethod(LockInfo info) { this.info = info; - this.isRefresh = false; } - // TODO: separate into two classes + // TODO: separate refresh info other class, builder? + /* LockMethod(long timeout, Collection lockTokens) { TimeoutHeader th = new TimeoutHeader(timeout); super.setHeader(th.getHeaderName(), th.getHeaderValue()); IfHeader ifh = new IfHeader(lockTokens); super.setHeader(ifh.getHeaderName(), ifh.getHeaderValue()); isRefresh = true; - } + }*/ @Override public Builder newRequestBuilder(URI uri) throws IOException { Builder builder = super.newRequestBuilder(uri); - if (isRefresh) { - builder.method(DavMethods.METHOD_LOCK, HttpRequest.BodyPublishers.noBody()); - TimeoutHeader th = new TimeoutHeader(timeout); - builder.setHeader(th.getHeaderName(), th.getHeaderValue()); - IfHeader ifh = new IfHeader(lockTokens); - builder.setHeader(ifh.getHeaderName(), ifh.getHeaderValue()); - } else { - builder.method(DavMethods.METHOD_LOCK, DavBodyPublishers.ofXmlSerializable(info)); - } + builder.method(DavMethods.METHOD_LOCK, DavBodyPublishers.ofXmlSerializable(info)); return builder; } @Override public BodyHandler newBodyResponseHandler() { - return newMultiStatusAwareBodyHandler(this::getLockToken); - } - - private String getLockToken(HttpHeaders headers) { - List ltHeader = headers.allValues(DavConstants.HEADER_LOCK_TOKEN); - if (ltHeader.size() != 1) { - LOG.debug("Multiple 'Lock-Token' header fields in response for " + response. + ": " + Arrays.asList(ltHeader)); - return null; - } else { - String v = ltHeader.get(0).trim(); - if (!v.startsWith("<") || !v.endsWith(">")) { - LOG.debug("Invalid 'Lock-Token' header field in response for " + getURI() + ": " + Arrays.asList(ltHeader)); - return null; - } else { - return v.substring(1, v.length() - 1); - } - } + // TODO: support multi-status response with lock token in case of failure + return DavBodyHandlers.ofLockToken(); } @Override diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLock.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLock.java index 97a31f235ea..171776d17c5 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLock.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLock.java @@ -40,7 +40,7 @@ */ public class HttpLock extends BaseDavRequest { - + private static final Logger LOG = LoggerFactory.getLogger(HttpLock.class); private final boolean isRefresh; diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/package-info.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/package-info.java index 5fa09379e1f..ca5f176d9b4 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/package-info.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/package-info.java @@ -16,7 +16,7 @@ */ /** - * Provides classes for use with the Java HttpClient, supporting WebDAV + * Provides classes for use with the Java {@link HttpClient}, supporting WebDAV * request methods. *

* The classes in this package are used to create and execute WebDAV requests like this diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/package-info.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/package-info.java index c31af121dcc..d5257e07018 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/package-info.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/package-info.java @@ -14,5 +14,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@org.osgi.annotation.versioning.Version("1.4.0") +@org.osgi.annotation.versioning.Version("1.5.0") package org.apache.jackrabbit.webdav; diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/package-info.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/package-info.java index 70c12ea510d..8c3aa785cd9 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/package-info.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/package-info.java @@ -14,5 +14,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@org.osgi.annotation.versioning.Version("1.2.0") +@org.osgi.annotation.versioning.Version("1.3.0") package org.apache.jackrabbit.webdav.xml;