diff --git a/ccm-core/src/main/java/org/libreccm/theming/webdav/AlreadyLockedException.java b/ccm-core/src/main/java/org/libreccm/theming/webdav/AlreadyLockedException.java
new file mode 100644
index 000000000..310b94fe5
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/theming/webdav/AlreadyLockedException.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+package org.libreccm.theming.webdav;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+class AlreadyLockedException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a new instance of AlreadyLockedException without detail message.
+ */
+ public AlreadyLockedException() {
+ super();
+ }
+
+
+ /**
+ * Constructs an instance of AlreadyLockedException with the specified detail message.
+ *
+ * @param msg The detail message.
+ */
+ public AlreadyLockedException(final String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs an instance of AlreadyLockedException which wraps the
+ * specified exception.
+ *
+ * @param exception The exception to wrap.
+ */
+ public AlreadyLockedException(final Exception exception) {
+ super(exception);
+ }
+
+ /**
+ * Constructs an instance of AlreadyLockedException with the specified message which also wraps the
+ * specified exception.
+ *
+ * @param msg The detail message.
+ * @param exception The exception to wrap.
+ */
+ public AlreadyLockedException(final String msg, final Exception exception) {
+ super(msg, exception);
+ }
+}
diff --git a/ccm-core/src/main/java/org/libreccm/theming/webdav/ThemeFiles.java b/ccm-core/src/main/java/org/libreccm/theming/webdav/ThemeFiles.java
index 689c6d379..e0f5f5517 100644
--- a/ccm-core/src/main/java/org/libreccm/theming/webdav/ThemeFiles.java
+++ b/ccm-core/src/main/java/org/libreccm/theming/webdav/ThemeFiles.java
@@ -41,16 +41,24 @@ import org.libreccm.theming.ThemeInfo;
import org.libreccm.theming.ThemeVersion;
import org.libreccm.theming.Themes;
import org.libreccm.webdav.ResponseStatus;
+import org.libreccm.webdav.methods.LOCK;
+import org.libreccm.webdav.xml.elements.ActiveLock;
import org.libreccm.webdav.xml.elements.Collection;
+import org.libreccm.webdav.xml.elements.Depth;
import org.libreccm.webdav.xml.elements.HRef;
+import org.libreccm.webdav.xml.elements.LockInfo;
+import org.libreccm.webdav.xml.elements.LockRoot;
+import org.libreccm.webdav.xml.elements.LockToken;
import org.libreccm.webdav.xml.elements.MultiStatus;
import org.libreccm.webdav.xml.elements.Prop;
import org.libreccm.webdav.xml.elements.PropStat;
import org.libreccm.webdav.xml.elements.Status;
+import org.libreccm.webdav.xml.elements.TimeOut;
import org.libreccm.webdav.xml.elements.WebDavResponse;
import org.libreccm.webdav.xml.properties.DisplayName;
import org.libreccm.webdav.xml.properties.GetContentLength;
import org.libreccm.webdav.xml.properties.GetContentType;
+import org.libreccm.webdav.xml.properties.LockDiscovery;
import java.util.LinkedList;
import java.util.List;
@@ -72,14 +80,17 @@ import javax.ws.rs.ext.Providers;
public class ThemeFiles {
@Inject
- private Themes themes;
+ private HttpServletRequest request;
@Inject
private ServletContext servletContext;
-
+
@Inject
- private HttpServletRequest request;
-
+ private Themes themes;
+
+ @Inject
+ private ThemeFilesLockManager lockManager;
+
@GET
@Path("/{path}")
public Response getFile(@PathParam("theme") final String theme,
@@ -117,6 +128,38 @@ public class ThemeFiles {
}
+ @LOCK
+ @Path("/{path}")
+ public Prop lock(@PathParam("theme")
+ final String theme,
+ @PathParam("path")
+ final String path,
+ final LockInfo lockInfo) {
+
+ final String lockedPath = String.format("%s/path",
+ theme,
+ path);
+
+ try {
+ final String lockToken = lockManager.lockFile(lockedPath);
+
+ return new Prop(new LockDiscovery(
+ new ActiveLock(lockInfo.getLockScope(),
+ lockInfo.getLockType(),
+ Depth.ZERO,
+ lockInfo.getOwner(),
+ new TimeOut(3600),
+ new LockToken(new HRef(String
+ .format("opaquelocktoken:%s",
+ lockToken))),
+ new LockRoot(new HRef(lockedPath)))));
+
+ } catch (AlreadyLockedException ex) {
+ throw new WebApplicationException(
+ ResponseStatus.LOCKED.getStatusCode());
+ }
+ }
+
@OPTIONS
public Response options() {
@@ -209,10 +252,10 @@ public class ThemeFiles {
return response;
}
-
+
private WebDavResponse buildWebDavResponse(final String basePath,
final ThemeFileInfo fileInfo) {
-
+
final PropStat propStat;
if (fileInfo.isDirectory()) {
propStat = new PropStat(
diff --git a/ccm-core/src/main/java/org/libreccm/theming/webdav/ThemeFilesLockManager.java b/ccm-core/src/main/java/org/libreccm/theming/webdav/ThemeFilesLockManager.java
new file mode 100644
index 000000000..c6f16c0ac
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/theming/webdav/ThemeFilesLockManager.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2018 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package org.libreccm.theming.webdav;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+
+import javax.enterprise.context.ApplicationScoped;
+
+/**
+ * Manages the locks on files for WebDAV.
+ *
+ * @author Jens Pelzetter
+ */
+@ApplicationScoped
+class ThemeFilesLockManager {
+
+ /**
+ * Mapping between file path and lock token
+ */
+ private final Map lockedFiles = new HashMap<>();
+
+ /**
+ * Mapping between lock token and file.
+ */
+ private final Map locks = new HashMap<>();
+
+ /**
+ * Lock a file
+ *
+ * @param file Path of the file to lock.
+ *
+ * @return The lock token.
+ *
+ * @throws AlreadyLockedException If the file is already locked.
+ */
+ protected String lockFile(final String file) throws AlreadyLockedException {
+
+ if (lockedFiles.containsKey(file)) {
+ throw new AlreadyLockedException(String.format(
+ "File %s is already locked.", file));
+ } else {
+
+ final String lockToken = UUID.randomUUID().toString();
+ lockedFiles.put(file, lockToken);
+ locks.put(lockToken, file);
+
+ return lockToken;
+ }
+ }
+
+ /**
+ * Check if a file is locked.
+ *
+ * @param file The file to check for a lock.
+ *
+ * @return An {@link Optional} with the lock token of the file if the file
+ * is locked, an empty {@code Optional} otherwise.
+ */
+ protected Optional isLocked(final String file) {
+ if (lockedFiles.containsKey(file)) {
+ return Optional.of(lockedFiles.get(file));
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * Removes the lock from a file.
+ *
+ * @param lockToken The token of the lock to remove.
+ */
+ protected void unlock(final String lockToken) {
+
+ final String file = locks.get(lockToken);
+ locks.remove(lockToken);
+ lockedFiles.remove(file);
+ }
+
+}