This is an automated email from the ASF dual-hosted git repository.

remm pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 27fe6a09c278e9bb3aeddf4047d23e7f999f9502
Author: remm <r...@apache.org>
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 fecf44bbcb..f60c50a7e1 100644
--- a/java/org/apache/catalina/servlets/LocalStrings.properties
+++ b/java/org/apache/catalina/servlets/LocalStrings.properties
@@ -62,3 +62,6 @@ webdavservlet.memorystore=Non persistent memory storage will 
be used for dead pr
 webdavservlet.noStoreParameter=Init parameter [{0}] with value [{1}] was not 
found on the configured store
 webdavservlet.nonWildcardMapping=The mapping [{0}] is not a wildcard mapping 
and should not be used for the WebDAV Servlet
 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 805f64a229..cd0401b0f5 100644
--- a/java/org/apache/catalina/servlets/WebdavServlet.java
+++ b/java/org/apache/catalina/servlets/WebdavServlet.java
@@ -2887,7 +2887,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 865595dfac..2dad54765d 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -218,6 +218,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: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to