Skip to content

Commit 928d41f

Browse files
committed
Basic LOCK/UNLOCK support for WebDAV
1 parent f668043 commit 928d41f

11 files changed

+320
-97
lines changed

Diff for: cloudata-files/src/main/java/com/cloudata/files/locks/CloudLock.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@
33
import java.util.concurrent.locks.Lock;
44

55
public interface CloudLock extends Lock {
6-
// CloudLockToken getLockToken();
7-
6+
CloudLockToken getLockToken();
87
}

Diff for: cloudata-files/src/main/java/com/cloudata/files/locks/InMemoryLockService.java

+75-61
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import java.util.List;
44
import java.util.Map;
55
import java.util.UUID;
6-
import java.util.concurrent.TimeUnit;
7-
import java.util.concurrent.locks.Condition;
86

97
import javax.inject.Singleton;
108

@@ -22,7 +20,7 @@ public Lockable(String key) {
2220
this.key = key;
2321
}
2422

25-
synchronized InMemoryLockToken lock() {
23+
synchronized InMemoryLockToken tryLock() {
2624
if (!locks.isEmpty()) {
2725
return null;
2826
}
@@ -33,106 +31,122 @@ synchronized InMemoryLockToken lock() {
3331
return lock;
3432
}
3533

36-
synchronized InMemoryLockToken unlock(String lockToken) {
34+
synchronized boolean unlock(String lockToken) {
3735
for (int i = 0; i < locks.size(); i++) {
3836
InMemoryLockToken lock = locks.get(i);
39-
if (lockToken.equals(lock.getLockToken())) {
37+
if (lockToken.equals(lock.getTokenId())) {
4038
locks.remove(i);
41-
return lock;
39+
return true;
4240
}
4341
}
44-
return null;
42+
return false;
4543
}
4644

4745
synchronized InMemoryLockToken find(String lockToken) {
4846
for (int i = 0; i < locks.size(); i++) {
4947
InMemoryLockToken lock = locks.get(i);
50-
if (lockToken.equals(lock.getLockToken())) {
48+
if (lockToken.equals(lock.getTokenId())) {
5149
return lock;
5250
}
5351
}
5452
return null;
5553
}
56-
}
57-
58-
/**
59-
* The lock implements the Java Lock. It is a lockable, not a held lock!
60-
*
61-
*/
62-
static class InMemoryLock implements CloudLock {
63-
final Lockable lockable;
64-
65-
public InMemoryLock(Lockable lockable) {
66-
this.lockable = lockable;
67-
}
68-
69-
@Override
70-
public void lock() {
71-
throw new UnsupportedOperationException();
72-
}
73-
74-
@Override
75-
public void lockInterruptibly() throws InterruptedException {
76-
throw new UnsupportedOperationException();
77-
}
78-
79-
@Override
80-
public boolean tryLock() {
81-
throw new UnsupportedOperationException();
82-
}
83-
84-
@Override
85-
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
86-
throw new UnsupportedOperationException();
87-
}
88-
89-
@Override
90-
public void unlock() {
91-
throw new UnsupportedOperationException();
92-
}
93-
94-
@Override
95-
public Condition newCondition() {
96-
throw new UnsupportedOperationException();
97-
}
9854

9955
}
10056

57+
// /**
58+
// * The lock implements the Java Lock. It is a lockable, not a held lock!
59+
// *
60+
// */
61+
// static class InMemoryLock implements CloudLock {
62+
// final Lockable lockable;
63+
//
64+
// public InMemoryLock(Lockable lockable) {
65+
// this.lockable = lockable;
66+
// }
67+
//
68+
// @Override
69+
// public void lock() {
70+
// throw new UnsupportedOperationException();
71+
// }
72+
//
73+
// @Override
74+
// public void lockInterruptibly() throws InterruptedException {
75+
// throw new UnsupportedOperationException();
76+
// }
77+
//
78+
// @Override
79+
// public boolean tryLock() {
80+
// throw new UnsupportedOperationException();
81+
// }
82+
//
83+
// @Override
84+
// public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
85+
// throw new UnsupportedOperationException();
86+
// }
87+
//
88+
// @Override
89+
// public void unlock() {
90+
// throw new UnsupportedOperationException();
91+
// }
92+
//
93+
// @Override
94+
// public Condition newCondition() {
95+
// throw new UnsupportedOperationException();
96+
// }
97+
//
98+
// @Override
99+
// public CloudLockToken getLockToken() {
100+
// throw new UnsupportedOperationException();
101+
// }
102+
//
103+
// }
104+
101105
/**
102106
* A LockToken represents a hold on a lock.
103107
*
104108
*/
105-
static class InMemoryLockToken {
109+
static class InMemoryLockToken implements CloudLockToken {
106110
final Lockable lock;
107-
final String token;
111+
final String tokenId;
108112

109-
public InMemoryLockToken(Lockable lock, String token) {
113+
public InMemoryLockToken(Lockable lock, String tokenId) {
110114
this.lock = lock;
111-
this.token = token;
115+
this.tokenId = tokenId;
112116
}
113117

114-
public String getLockToken() {
115-
return token;
118+
@Override
119+
public String getTokenId() {
120+
return tokenId;
116121
}
117122
}
118123

119-
final Map<String, InMemoryLock> locks = Maps.newHashMap();
124+
final Map<String, Lockable> locks = Maps.newHashMap();
120125

121-
@Override
122-
public InMemoryLock get(String key) {
123-
InMemoryLock lockset;
126+
private Lockable getLockable(String key) {
127+
Lockable lockset;
124128
synchronized (locks) {
125129
lockset = locks.get(key);
126130

127131
if (lockset == null) {
128-
lockset = new InMemoryLock(new Lockable(key));
132+
lockset = new Lockable(key);
129133
locks.put(key, lockset);
130134
}
131135
}
132136

133137
return lockset;
134138
}
135139

140+
@Override
141+
public CloudLockToken tryLock(String key) {
142+
return getLockable(key).tryLock();
143+
}
144+
145+
@Override
146+
public boolean unlock(String key, String lockToken) {
147+
return getLockable(key).unlock(lockToken);
148+
}
149+
136150
// @Override
137151
// public InMemoryLockToken lock(String uri) {
138152
// LockSet lockset;
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package com.cloudata.files.locks;
22

33
public interface LockService {
4-
CloudLock get(String key);
54

6-
// CloudLockToken lock(String key);
7-
//
8-
// CloudLockToken unlock(String key, String lockToken);
9-
//
10-
// CloudLockToken findLock(String key, String lockToken);
5+
CloudLockToken tryLock(String key);
6+
7+
boolean unlock(String key, String lockToken);
118

129
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.cloudata.files.webdav;
2+
3+
import io.netty.handler.codec.http.HttpObject;
4+
import io.netty.handler.codec.http.HttpResponse;
5+
import io.netty.handler.codec.http.HttpResponseStatus;
6+
7+
import java.io.IOException;
8+
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
11+
12+
import com.cloudata.files.fs.FsPath;
13+
import com.cloudata.files.locks.CloudLockToken;
14+
import com.cloudata.files.locks.LockService;
15+
import com.cloudata.files.webdav.model.LockRequest;
16+
import com.cloudata.files.webdav.model.LockResponse;
17+
18+
public class LockHandler extends MethodHandler {
19+
private static final Logger log = LoggerFactory.getLogger(LockHandler.class);
20+
21+
public static final String LOCK_PREFIX = "opaquelocktoken:";
22+
23+
public LockHandler(WebdavRequestHandler requestHandler) {
24+
super(requestHandler);
25+
}
26+
27+
@Override
28+
protected HttpObject doAction(FsPath fsPath) throws Exception {
29+
LockService lockService = getLockService();
30+
WebdavRequest request = getRequest();
31+
32+
LockRequest lockRequest;
33+
try {
34+
lockRequest = new LockRequest(request);
35+
} catch (IOException e) {
36+
log.warn("Error parsing request", e);
37+
throw new IllegalArgumentException();
38+
}
39+
40+
log.debug("LockRequest: " + lockRequest);
41+
42+
CloudLockToken lockToken = lockService.tryLock(request.getUri());
43+
if (lockToken == null) {
44+
throw new WebdavResponseException(HttpResponseStatus.LOCKED);
45+
}
46+
47+
String href = LOCK_PREFIX + lockToken.getTokenId();
48+
LockResponse lockResponse = new LockResponse(href);
49+
HttpResponse response = buildXmlResponse(HttpResponseStatus.OK, lockResponse);
50+
response.headers().set("Lock-Token", "<" + href + ">");
51+
return response;
52+
}
53+
54+
}

Diff for: cloudata-files/src/main/java/com/cloudata/files/webdav/MethodHandler.java

+8
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.cloudata.files.fs.FsCredentials;
3131
import com.cloudata.files.fs.FsPath;
3232
import com.cloudata.files.fs.FsVolume;
33+
import com.cloudata.files.locks.LockService;
3334
import com.cloudata.files.webdav.chunks.ChunkAccumulator;
3435
import com.cloudata.files.webdav.model.WebdavXmlWriter;
3536
import com.cloudata.files.webdav.model.XmlSerializable;
@@ -95,12 +96,19 @@ protected boolean requiresValidPath() {
9596

9697
protected abstract HttpObject doAction(FsPath fsPath) throws Exception;
9798

99+
protected LockService getLockService() {
100+
return this.requestHandler.webdav.lockService;
101+
}
102+
98103
protected ListenableFuture<HttpObject> doActionAsync(FsPath fsPath) throws Exception {
99104
return Futures.immediateFuture(doAction(fsPath));
100105
}
101106

102107
private boolean checkIfHeader(String ifHeader) {
103108
throw new UnsupportedOperationException();
109+
110+
// (<opaquelocktoken:6de84ba6-7063-477b-9ca6-a246ce69ed8b>)
111+
104112
//
105113
// // See: http://www.webdav.org/specs/rfc2518.html#HEADER_If
106114
// log.warn("If-Header parsing is totally stubbed out");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.cloudata.files.webdav;
2+
3+
import io.netty.handler.codec.http.FullHttpResponse;
4+
import io.netty.handler.codec.http.HttpObject;
5+
import io.netty.handler.codec.http.HttpResponseStatus;
6+
7+
import com.cloudata.files.fs.FsPath;
8+
import com.cloudata.files.locks.LockService;
9+
import com.google.common.base.Strings;
10+
11+
public class UnlockHandler extends MethodHandler {
12+
13+
public UnlockHandler(WebdavRequestHandler requestHandler) {
14+
super(requestHandler);
15+
}
16+
17+
@Override
18+
protected HttpObject doAction(FsPath fsPath) throws Exception {
19+
LockService lockService = getLockService();
20+
WebdavRequest request = getRequest();
21+
22+
String lockToken = request.getHeader("Lock-Token");
23+
if (Strings.isNullOrEmpty(lockToken)) {
24+
throw new IllegalArgumentException("Lock-Token is required");
25+
}
26+
27+
lockToken = lockToken.trim();
28+
29+
if (lockToken.startsWith("<")) {
30+
lockToken = lockToken.substring(1).trim();
31+
}
32+
33+
if (lockToken.startsWith(LockHandler.LOCK_PREFIX)) {
34+
lockToken = lockToken.substring(LockHandler.LOCK_PREFIX.length()).trim();
35+
}
36+
37+
if (lockToken.endsWith(">")) {
38+
lockToken = lockToken.substring(0, lockToken.length() - 1);
39+
}
40+
41+
boolean unlocked = lockService.unlock(request.getUri(), lockToken);
42+
if (!unlocked) {
43+
throw new IllegalArgumentException("Lock not found");
44+
}
45+
46+
FullHttpResponse response = buildFullResponse(HttpResponseStatus.NO_CONTENT);
47+
return response;
48+
}
49+
}

0 commit comments

Comments
 (0)