This is an automated email from the ASF dual-hosted git repository.
remm pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/11.0.x by this push:
new f3988d16ed Add persistent PropertyStore for WebDAV
f3988d16ed is described below
commit f3988d16ed7f4945d0f3f7da1de83b729cb501e0
Author: remm <[email protected]>
AuthorDate: Thu Dec 5 10:25:40 2024 +0100
Add persistent PropertyStore for WebDAV
---
.../catalina/servlets/DataSourcePropertyStore.java | 356 +++++++++++++++++++++
.../catalina/servlets/LocalStrings.properties | 3 +
.../apache/catalina/servlets/WebdavServlet.java | 2 +-
java/org/apache/catalina/util/DOMWriter.java | 4 +
.../catalina/servlets/TestWebdavPropertyStore.java | 245 ++++++++++++++
webapps/docs/changelog.xml | 4 +
6 files changed, 613 insertions(+), 1 deletion(-)
diff --git a/java/org/apache/catalina/servlets/DataSourcePropertyStore.java
b/java/org/apache/catalina/servlets/DataSourcePropertyStore.java
new file mode 100644
index 0000000000..397f365c81
--- /dev/null
+++ b/java/org/apache/catalina/servlets/DataSourcePropertyStore.java
@@ -0,0 +1,356 @@
+/*
+ * 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.
+ */
+package org.apache.catalina.servlets;
+
+import java.io.StringWriter;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.sql.DataSource;
+
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.servlets.WebdavServlet.PropertyUpdateType;
+import org.apache.catalina.servlets.WebdavServlet.ProppatchOperation;
+import org.apache.catalina.util.DOMWriter;
+import org.apache.catalina.util.XMLWriter;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+import org.w3c.dom.Node;
+
+/**
+ * WebDAV dead properties storage backed by a DataSource. Usually table and
column names
+ * are configurable, but for simplicity this is not the case.
+ * The schema is:
+ * table properties ( path, namespace, name, node )
+ * path: the resource path
+ * namespace: the node namespace
+ * name: the local name in the namespace
+ * node: the full serialized XML node including the name
+ */
+public class DataSourcePropertyStore implements WebdavServlet.PropertyStore {
+
+ protected static final StringManager sm =
StringManager.getManager(DataSourcePropertyStore.class);
+ private final Log log = LogFactory.getLog(DataSourcePropertyStore.class);
+
+ private static String ADD_PROPERTY_STMT = "INSERT INTO properties (path,
namespace, name, node) VALUES (?, ?, ?, ?)";
+ private static String SET_PROPERTY_STMT = "UPDATE properties SET node = ?
WHERE path = ? AND namespace = ? AND name = ?";
+ private static String REMOVE_ALL_PROPERTIES_STMT = "DELETE FROM properties
WHERE path = ?";
+ private static String REMOVE_PROPERTY_STMT = "DELETE FROM properties WHERE
path = ? AND namespace = ? AND name = ?";
+ private static String GET_PROPERTY_STMT = "SELECT node FROM properties
WHERE path = ? AND namespace = ? AND name = ?";
+ private static String GET_PROPERTIES_NAMES_STMT = "SELECT namespace, name
FROM properties WHERE path = ?";
+ private static String GET_PROPERTIES_STMT = "SELECT namespace, name, node
FROM properties WHERE path = ?";
+ private static String GET_PROPERTIES_NODES_STMT = "SELECT node FROM
properties WHERE path = ?";
+
+ /**
+ * DataSource JNDI name, will be prefixed with java:comp/env for the
lookup.
+ */
+ private String dataSourceName = "WebdavPropertyStore";
+
+ private final ReentrantReadWriteLock dbLock = new ReentrantReadWriteLock();
+ private final Lock dbReadLock = dbLock.readLock();
+ private final Lock dbWriteLock = dbLock.writeLock();
+
+ /**
+ * @return the dataSourceName
+ */
+ public String getDataSourceName() {
+ return this.dataSourceName;
+ }
+
+ /**
+ * @param dataSourceName the dataSourceName to set
+ */
+ public void setDataSourceName(String dataSourceName) {
+ this.dataSourceName = dataSourceName;
+ }
+
+ /**
+ * DataSource instance being used.
+ */
+ protected DataSource dataSource = null;
+
+ @Override
+ public void init() {
+ if (dataSource == null) {
+ try {
+ dataSource = (DataSource) ((new
InitialContext()).lookup("java:comp/env/" + dataSourceName));
+ } catch (NamingException e) {
+ throw new
IllegalArgumentException(sm.getString("webdavservlet.dataSourceStore.noDataSource",
dataSourceName), e);
+ }
+ }
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ @Override
+ public void periodicEvent() {
+ }
+
+ @Override
+ public void copy(String source, String destination) {
+ if (dataSource == null) {
+ return;
+ }
+ dbWriteLock.lock();
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement statement =
connection.prepareStatement(GET_PROPERTIES_STMT)) {
+ statement.setString(1, source);
+ if (statement.execute()) {
+ ResultSet rs = statement.getResultSet();
+ while (rs.next()) {
+ String namespace = rs.getString(1);
+ String name = rs.getString(2);
+ String node = rs.getString(3);
+ boolean found = false;
+ try (PreparedStatement statement2 =
connection.prepareStatement(GET_PROPERTY_STMT)) {
+ statement2.setString(1, destination);
+ statement2.setString(2, namespace);
+ statement2.setString(3, name);
+ if (statement2.execute()) {
+ ResultSet rs2 = statement2.getResultSet();
+ if (rs2.next()) {
+ found = true;
+ }
+ }
+ }
+ if (found) {
+ try (PreparedStatement statement2 =
connection.prepareStatement(SET_PROPERTY_STMT)) {
+ statement2.setString(1, node);
+ statement2.setString(2, destination);
+ statement2.setString(3, namespace);
+ statement2.setString(4, name);
+ statement2.execute();
+ }
+ } else {
+ try (PreparedStatement statement2 =
connection.prepareStatement(ADD_PROPERTY_STMT)) {
+ statement2.setString(1, destination);
+ statement2.setString(2, namespace);
+ statement2.setString(3, name);
+ statement2.setString(4, node);
+ statement2.execute();
+ }
+ }
+ }
+ }
+ } catch (SQLException e) {
+ log.warn(sm.getString("webdavservlet.dataSourceStore.error",
"copy", source), e);
+ } finally {
+ dbWriteLock.unlock();
+ }
+ }
+
+ @Override
+ public void delete(String resource) {
+ if (dataSource == null) {
+ return;
+ }
+ dbWriteLock.lock();
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement statement =
connection.prepareStatement(REMOVE_ALL_PROPERTIES_STMT)) {
+ statement.setString(1, resource);
+ statement.execute();
+ } catch (SQLException e) {
+ log.warn(sm.getString("webdavservlet.dataSourceStore.error",
"delete", resource), e);
+ } finally {
+ dbWriteLock.unlock();
+ }
+ }
+
+ @Override
+ public boolean propfind(String resource, Node property, boolean nameOnly,
XMLWriter generatedXML) {
+ if (dataSource == null) {
+ return false;
+ }
+ if (nameOnly) {
+ // Add the names of all properties
+ dbReadLock.lock();
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement statement =
connection.prepareStatement(GET_PROPERTIES_NAMES_STMT)) {
+ statement.setString(1, resource);
+ if (statement.execute()) {
+ ResultSet rs = statement.getResultSet();
+ while (rs.next()) {
+ String namespace = rs.getString(1);
+ String name = rs.getString(2);
+ generatedXML.writeElement(null, namespace, name,
XMLWriter.NO_CONTENT);
+ }
+ }
+ } catch (SQLException e) {
+ log.warn(sm.getString("webdavservlet.dataSourceStore.error",
"propfind", resource), e);
+ } finally {
+ dbReadLock.unlock();
+ }
+ } else if (property != null) {
+ // Add a single property
+ dbReadLock.lock();
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement statement =
connection.prepareStatement(GET_PROPERTY_STMT)) {
+ statement.setString(1, resource);
+ statement.setString(2, property.getNamespaceURI());
+ statement.setString(3, property.getLocalName());
+ if (statement.execute()) {
+ ResultSet rs = statement.getResultSet();
+ if (rs.next()) {
+ String node = rs.getString(1);
+ generatedXML.writeRaw(node);
+ return true;
+ }
+ }
+ } catch (SQLException e) {
+ log.warn(sm.getString("webdavservlet.dataSourceStore.error",
"propfind", resource), e);
+ } finally {
+ dbReadLock.unlock();
+ }
+ } else {
+ // Add all properties
+ dbReadLock.lock();
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement statement =
connection.prepareStatement(GET_PROPERTIES_NODES_STMT)) {
+ statement.setString(1, resource);
+ if (statement.execute()) {
+ ResultSet rs = statement.getResultSet();
+ while (rs.next()) {
+ String node = rs.getString(1);
+ generatedXML.writeRaw(node);
+ }
+ }
+ } catch (SQLException e) {
+ log.warn(sm.getString("webdavservlet.dataSourceStore.error",
"propfind", resource), e);
+ } finally {
+ dbReadLock.unlock();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void proppatch(String resource, ArrayList<ProppatchOperation>
operations) {
+ boolean protectedProperty = false;
+ // Check for the protected properties
+ for (ProppatchOperation operation : operations) {
+ if (operation.getProtectedProperty()) {
+ protectedProperty = true;
+ operation.setStatusCode(HttpServletResponse.SC_FORBIDDEN);
+ }
+ }
+ if (protectedProperty) {
+ for (ProppatchOperation operation : operations) {
+ if (!operation.getProtectedProperty()) {
+ operation.setStatusCode(WebdavStatus.SC_FAILED_DEPENDENCY);
+ }
+ }
+ } else {
+ if (dataSource == null) {
+ for (ProppatchOperation operation : operations) {
+
operation.setStatusCode(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ }
+ return;
+ }
+ boolean failure = false;
+ dbWriteLock.lock();
+ try (Connection connection = dataSource.getConnection()) {
+ connection.setAutoCommit(false);
+ for (ProppatchOperation operation : operations) {
+ if (operation.getUpdateType() == PropertyUpdateType.SET) {
+ Node node =
operation.getPropertyNode().cloneNode(true);
+ StringWriter strWriter = new StringWriter();
+ DOMWriter domWriter = new DOMWriter(strWriter);
+ domWriter.print(node);
+ String serializedNode = strWriter.toString();
+ boolean found = false;
+ try {
+ try (PreparedStatement statement =
connection.prepareStatement(GET_PROPERTY_STMT)) {
+ statement.setString(1, resource);
+ statement.setString(2, node.getNamespaceURI());
+ statement.setString(3, node.getLocalName());
+ if (statement.execute()) {
+ ResultSet rs = statement.getResultSet();
+ if (rs.next()) {
+ found = true;
+ }
+ }
+ }
+ if (found) {
+ try (PreparedStatement statement =
connection.prepareStatement(SET_PROPERTY_STMT)) {
+ statement.setString(1, serializedNode);
+ statement.setString(2, resource);
+ statement.setString(3,
node.getNamespaceURI());
+ statement.setString(4,
node.getLocalName());
+ statement.execute();
+ }
+ } else {
+ try (PreparedStatement statement =
connection.prepareStatement(ADD_PROPERTY_STMT)) {
+ statement.setString(1, resource);
+ statement.setString(2,
node.getNamespaceURI());
+ statement.setString(3,
node.getLocalName());
+ statement.setString(4, serializedNode);
+ statement.execute();
+ }
+ }
+ } catch (SQLException e) {
+ failure = true;
+
operation.setStatusCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ break;
+ }
+ }
+ if (operation.getUpdateType() ==
PropertyUpdateType.REMOVE) {
+ Node node = operation.getPropertyNode();
+ try (PreparedStatement statement =
connection.prepareStatement(REMOVE_PROPERTY_STMT)) {
+ statement.setString(1, resource);
+ statement.setString(2, node.getNamespaceURI());
+ statement.setString(3, node.getLocalName());
+ statement.execute();
+ } catch (SQLException e) {
+ failure = true;
+
operation.setStatusCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ break;
+ }
+ }
+ }
+ if (failure) {
+ connection.rollback();
+ for (ProppatchOperation operation : operations) {
+ if (operation.getStatusCode() ==
HttpServletResponse.SC_OK) {
+
operation.setStatusCode(WebdavStatus.SC_FAILED_DEPENDENCY);
+ }
+ }
+ } else {
+ connection.commit();
+ }
+ } catch (SQLException e) {
+ log.warn(sm.getString("webdavservlet.dataSourceStore.error",
"proppatch", resource), e);
+ for (ProppatchOperation operation : operations) {
+
operation.setStatusCode(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ }
+ } finally {
+ dbWriteLock.unlock();
+ }
+ }
+ }
+
+}
diff --git a/java/org/apache/catalina/servlets/LocalStrings.properties
b/java/org/apache/catalina/servlets/LocalStrings.properties
index ed67a34049..6f172cbbc8 100644
--- a/java/org/apache/catalina/servlets/LocalStrings.properties
+++ b/java/org/apache/catalina/servlets/LocalStrings.properties
@@ -61,3 +61,6 @@ webdavservlet.memorystore=Non persistent memory storage will
be used for dead pr
webdavservlet.nonWildcardMapping=The mapping [{0}] is not a wildcard mapping
and should not be used for the WebDAV Servlet
webdavservlet.noStoreParameter=Init parameter [{0}] with value [{1}] was not
found on the configured store
webdavservlet.storeError=Error creating store of class [{0}], the default
memory store will be used instead
+
+webdavservlet.dataSourceStore.error=Error processing [{0}] on dead properties
for path [{1}]
+webdavservlet.dataSourceStore.noDataSource=DataSource [{0}] was not found in
the webapp environment
diff --git a/java/org/apache/catalina/servlets/WebdavServlet.java
b/java/org/apache/catalina/servlets/WebdavServlet.java
index 6d4a8f3379..c7f7cfad55 100644
--- a/java/org/apache/catalina/servlets/WebdavServlet.java
+++ b/java/org/apache/catalina/servlets/WebdavServlet.java
@@ -2888,7 +2888,7 @@ public class WebdavServlet extends DefaultServlet
implements PeriodicEventListen
/**
* Default property store, which provides memory storage without
persistence.
*/
- private class MemoryPropertyStore implements PropertyStore {
+ public static class MemoryPropertyStore implements PropertyStore {
private final ConcurrentHashMap<String,ArrayList<Node>> deadProperties
= new ConcurrentHashMap<>();
diff --git a/java/org/apache/catalina/util/DOMWriter.java
b/java/org/apache/catalina/util/DOMWriter.java
index 7ef95b1ffa..abedc0355b 100644
--- a/java/org/apache/catalina/util/DOMWriter.java
+++ b/java/org/apache/catalina/util/DOMWriter.java
@@ -66,6 +66,10 @@ public class DOMWriter {
Attr attrs[] = sortAttributes(node.getAttributes());
boolean xmlns = false;
for (Attr attr : attrs) {
+ if ("xmlns".equals(attr.getPrefix())) {
+ // Skip namespace prefixes as they are removed
+ continue;
+ }
out.print(' ');
out.print(attr.getLocalName());
if ("xmlns".equals(attr.getLocalName())) {
diff --git a/test/org/apache/catalina/servlets/TestWebdavPropertyStore.java
b/test/org/apache/catalina/servlets/TestWebdavPropertyStore.java
new file mode 100644
index 0000000000..3f03ff710e
--- /dev/null
+++ b/test/org/apache/catalina/servlets/TestWebdavPropertyStore.java
@@ -0,0 +1,245 @@
+/*
+ * 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.
+ */
+package org.apache.catalina.servlets;
+
+import java.io.ByteArrayInputStream;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+import org.apache.catalina.servlets.WebdavServlet.PropertyStore;
+import org.apache.catalina.servlets.WebdavServlet.PropertyUpdateType;
+import org.apache.catalina.servlets.WebdavServlet.ProppatchOperation;
+import org.apache.catalina.startup.LoggingBaseTest;
+import org.apache.catalina.util.XMLWriter;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+
+@RunWith(Parameterized.class)
+public class TestWebdavPropertyStore extends LoggingBaseTest {
+
+ private static final String PROPERTY1 =
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
+ "<T:customprop xmlns:T=\"http://tomcat.apache.org/testsuite\">\n" +
+ " <T:myvalue/>\n" +
+ "</T:customprop>";
+
+ private static final String PROPERTY2 =
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
+ "<V:someprop xmlns:V=\"http://tomcat.apache.org/other\">\n" +
+ " <V:myvalue>bla</V:myvalue>\n" +
+ "</V:someprop>";
+
+ private static final String PROPERTY3 =
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
+ "<V:someprop xmlns:V=\"http://tomcat.apache.org/other\">\n" +
+ " <V:othervalue>foooooooo</V:othervalue>\n" +
+ "</V:someprop>";
+
+ public static final String SIMPLE_SCHEMA =
+ "create table properties (\n" +
+ " path varchar(256) not null,\n" +
+ " namespace varchar(64) not null,\n" +
+ " name varchar(64) not null,\n" +
+ " node varchar(1024) not null" +
+ ")";
+
+ public static class CustomDataSourcePropertyStore extends
DataSourcePropertyStore {
+ public void setDataSource(DataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+ }
+
+ private class DerbyDataSource implements DataSource {
+
+ Connection connection = null;
+
+ DerbyDataSource() {
+ try {
+ Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
+ connection = DriverManager.getConnection("jdbc:derby:" +
getTemporaryDirectory().getAbsolutePath()
+ + "/webdavproperties;create=true");
+ try (Statement statement = connection.createStatement()) {
+ statement.execute(SIMPLE_SCHEMA);
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public Logger getParentLogger() throws SQLFeatureNotSupportedException
{
+ return null;
+ }
+
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException {
+ return null;
+ }
+
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException {
+ return false;
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ if (connection.isClosed()) {
+ connection = DriverManager.getConnection("jdbc:derby:" +
getTemporaryDirectory().getAbsolutePath()
+ + "/webdavproperties");
+ }
+ return connection;
+ }
+
+ @Override
+ public Connection getConnection(String username, String password)
throws SQLException {
+ return getConnection();
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws SQLException {
+ return null;
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter out) throws SQLException {
+ }
+
+ @Override
+ public void setLoginTimeout(int seconds) throws SQLException {
+ }
+
+ @Override
+ public int getLoginTimeout() throws SQLException {
+ return 0;
+ }
+
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> parameters() {
+ List<Object[]> parameterSets = new ArrayList<>();
+ parameterSets.add(new Object[] {
"org.apache.catalina.servlets.WebdavServlet$MemoryPropertyStore" });
+ parameterSets.add(new Object[] {
"org.apache.catalina.servlets.TestWebdavPropertyStore$CustomDataSourcePropertyStore"
});
+ return parameterSets;
+ }
+
+ @Parameter(0)
+ public String storeName;
+
+ @Test
+ public void testStore() throws Exception {
+ DocumentBuilderFactory documentBuilderFactory =
DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ documentBuilderFactory.setExpandEntityReferences(false);
+ DocumentBuilder documentBuilder =
documentBuilderFactory.newDocumentBuilder();
+ Document document1 = documentBuilder.parse(new InputSource(new
ByteArrayInputStream(PROPERTY1.getBytes(StandardCharsets.UTF_8))));
+ Node node1 = document1.getDocumentElement();
+ Document document2 = documentBuilder.parse(new InputSource(new
ByteArrayInputStream(PROPERTY2.getBytes(StandardCharsets.UTF_8))));
+ Node node2 = document2.getDocumentElement();
+ Document document3 = documentBuilder.parse(new InputSource(new
ByteArrayInputStream(PROPERTY3.getBytes(StandardCharsets.UTF_8))));
+ Node node3 = document3.getDocumentElement();
+
+ PropertyStore propertyStore = (PropertyStore)
Class.forName(storeName).getDeclaredConstructor().newInstance();
+ if (propertyStore instanceof CustomDataSourcePropertyStore) {
+ ((CustomDataSourcePropertyStore) propertyStore).setDataSource(new
DerbyDataSource());
+ }
+
+ // Add properties
+ ArrayList<ProppatchOperation> operations = new ArrayList<>();
+ operations.add(new ProppatchOperation(PropertyUpdateType.SET, node1));
+ operations.add(new ProppatchOperation(PropertyUpdateType.SET, node2));
+ propertyStore.proppatch("/some/path1", operations);
+
+ // Add properties
+ operations = new ArrayList<>();
+ operations.add(new ProppatchOperation(PropertyUpdateType.SET, node1));
+ propertyStore.proppatch("/other/path2", operations);
+
+ // Get single property
+ XMLWriter xmlWriter1 = new XMLWriter();
+ Assert.assertTrue(propertyStore.propfind("/some/path1", node1, false,
xmlWriter1));
+ Assert.assertTrue(xmlWriter1.toString().contains("<myvalue "));
+
+ // Get property names
+ XMLWriter xmlWriter2 = new XMLWriter();
+ Assert.assertFalse(propertyStore.propfind("/some/path1", null, true,
xmlWriter2));
+ Assert.assertTrue(xmlWriter2.toString().contains("<someprop"));
+
+ // Get all properties
+ XMLWriter xmlWriter3 = new XMLWriter();
+ Assert.assertFalse(propertyStore.propfind("/some/path1", null, false,
xmlWriter3));
+ Assert.assertTrue(xmlWriter3.toString().contains(">bla</myvalue>"));
+
+ propertyStore.copy("/some/path1", "/some/path2");
+ XMLWriter xmlWriter4 = new XMLWriter();
+ Assert.assertFalse(propertyStore.propfind("/some/path2", null, true,
xmlWriter4));
+ Assert.assertTrue(xmlWriter4.toString().contains("<someprop"));
+
+ propertyStore.delete("/some/path1");
+ XMLWriter xmlWriter5 = new XMLWriter();
+ Assert.assertFalse(propertyStore.propfind("/some/path1", null, true,
xmlWriter5));
+ Assert.assertTrue(xmlWriter5.toString().isEmpty());
+
+ propertyStore.copy("/some/path2", "/other/path2");
+ XMLWriter xmlWriter6 = new XMLWriter();
+ Assert.assertFalse(propertyStore.propfind("/other/path2", null, true,
xmlWriter6));
+ Assert.assertTrue(xmlWriter6.toString().contains("<customprop"));
+
+ operations = new ArrayList<>();
+ operations.add(new ProppatchOperation(PropertyUpdateType.REMOVE,
node1));
+ propertyStore.proppatch("/other/path2", operations);
+
+ XMLWriter xmlWriter7 = new XMLWriter();
+ Assert.assertFalse(propertyStore.propfind("/other/path2", null, false,
xmlWriter7));
+ Assert.assertFalse(xmlWriter7.toString().contains("<customprop"));
+ Assert.assertTrue(xmlWriter7.toString().contains(">bla</myvalue>"));
+
+ operations = new ArrayList<>();
+ operations.add(new ProppatchOperation(PropertyUpdateType.SET, node3));
+ propertyStore.proppatch("/other/path2", operations);
+
+ XMLWriter xmlWriter8 = new XMLWriter();
+ Assert.assertFalse(propertyStore.propfind("/other/path2", null, false,
xmlWriter8));
+ Assert.assertFalse(xmlWriter8.toString().contains("<customprop"));
+
Assert.assertTrue(xmlWriter8.toString().contains(">foooooooo</othervalue>"));
+
+ XMLWriter xmlWriter9 = new XMLWriter();
+ Assert.assertFalse(propertyStore.propfind("/other/path2", node1,
false, xmlWriter9));
+ Assert.assertTrue(xmlWriter9.toString().isEmpty());
+
+ }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 900ddb9317..a1e359e1c8 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -228,6 +228,10 @@
the default servlet. It will be removed in Tomcat 12 onwards where it
will effectively be hard coded to <code>true</code>. (markt)
</fix>
+ <add>
+ Add <code>DataSource</code> based property storage for the
+ <code>WebdavServlet</code>. (remm)
+ </add>
</changelog>
</subsection>
<subsection name="Coyote">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]