1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package org.modeshape.web.jcr.webdav;
25
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.security.Principal;
29 import java.util.Arrays;
30 import java.util.Calendar;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.Date;
34 import java.util.HashSet;
35 import java.util.LinkedList;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.WeakHashMap;
40 import javax.jcr.Binary;
41 import javax.jcr.Item;
42 import javax.jcr.Node;
43 import javax.jcr.NodeIterator;
44 import javax.jcr.PathNotFoundException;
45 import javax.jcr.Property;
46 import javax.jcr.RepositoryException;
47 import javax.jcr.Session;
48 import javax.servlet.http.HttpServletRequest;
49 import net.sf.webdav.ITransaction;
50 import net.sf.webdav.IWebdavStore;
51 import net.sf.webdav.StoredObject;
52 import net.sf.webdav.exceptions.WebdavException;
53 import org.modeshape.common.util.CheckArg;
54 import org.modeshape.common.util.IoUtil;
55 import org.modeshape.graph.mimetype.MimeTypeDetector;
56 import org.modeshape.mimetype.aperture.ApertureMimeTypeDetector;
57 import org.modeshape.web.jcr.RepositoryFactory;
58
59
60
61
62
63
64
65 public class ModeShapeWebdavStore implements IWebdavStore {
66
67 private static final String[] EMPTY_STRING_ARRAY = new String[0];
68
69 private static final ThreadLocal<HttpServletRequest> THREAD_LOCAL_REQUEST = new ThreadLocal<HttpServletRequest>();
70
71
72 private static final Map<String, byte[]> OSX_DOUBLE_DATA = Collections.synchronizedMap(new WeakHashMap<String, byte[]>());
73
74 private static final String CONTENT_NODE_NAME = "jcr:content";
75 private static final String DATA_PROP_NAME = "jcr:data";
76 private static final String CREATED_PROP_NAME = "jcr:created";
77 private static final String MODIFIED_PROP_NAME = "jcr:lastModified";
78 private static final String ENCODING_PROP_NAME = "jcr:encoding";
79 private static final String MIME_TYPE_PROP_NAME = "jcr:mimeType";
80
81 private static final String DEFAULT_CONTENT_PRIMARY_TYPES = "nt:resource, mode:resource";
82 private static final String DEFAULT_RESOURCE_PRIMARY_TYPES = "nt:file";
83 private static final String DEFAULT_NEW_FOLDER_PRIMARY_TYPE = "nt:folder";
84 private static final String DEFAULT_NEW_RESOURCE_PRIMARY_TYPE = "nt:file";
85 private static final String DEFAULT_NEW_CONTENT_PRIMARY_TYPE = "mode:resource";
86
87 private final Collection<String> contentPrimaryTypes;
88 private final Collection<String> filePrimaryTypes;
89 private final String newFolderPrimaryType;
90 private final String newResourcePrimaryType;
91 private final String newContentPrimaryType;
92 private final MimeTypeDetector mimeTypeDetector = new ApertureMimeTypeDetector();
93 private final RequestResolver uriResolver;
94
95 public ModeShapeWebdavStore( RequestResolver uriResolver ) {
96 this(null, null, null, null, null, uriResolver);
97 }
98
99 public ModeShapeWebdavStore( String contentPrimaryTypes,
100 String filePrimaryTypes,
101 String newFolderPrimaryType,
102 String newResourcePrimaryType,
103 String newContentPrimaryType,
104 RequestResolver uriResolver ) {
105 super();
106 this.contentPrimaryTypes = split(contentPrimaryTypes != null ? contentPrimaryTypes : DEFAULT_CONTENT_PRIMARY_TYPES);
107 this.filePrimaryTypes = split(filePrimaryTypes != null ? filePrimaryTypes : DEFAULT_RESOURCE_PRIMARY_TYPES);
108 this.newFolderPrimaryType = newFolderPrimaryType != null ? newFolderPrimaryType : DEFAULT_NEW_FOLDER_PRIMARY_TYPE;
109 this.newResourcePrimaryType = newResourcePrimaryType != null ? newResourcePrimaryType : DEFAULT_NEW_RESOURCE_PRIMARY_TYPE;
110 this.newContentPrimaryType = newContentPrimaryType != null ? newContentPrimaryType : DEFAULT_NEW_CONTENT_PRIMARY_TYPE;
111
112 this.uriResolver = uriResolver;
113 }
114
115
116
117
118
119
120
121 private static final Set<String> setFor( String... elements ) {
122 Set<String> set = new HashSet<String>(elements.length);
123 set.addAll(Arrays.asList(elements));
124
125 return set;
126 }
127
128
129
130
131
132
133
134
135 private static final Set<String> split( String commaDelimitedString ) {
136 return setFor(commaDelimitedString.split("\\s*,\\s*"));
137 }
138
139
140
141
142
143
144 static final void setRequest( HttpServletRequest request ) {
145 THREAD_LOCAL_REQUEST.set(request);
146 }
147
148
149
150
151 @Override
152 public ITransaction begin( Principal principal ) {
153 return new JcrSessionTransaction(principal);
154 }
155
156
157
158
159 @Override
160 public void commit( ITransaction transaction ) {
161 CheckArg.isNotNull(transaction, "transaction");
162
163 assert transaction instanceof JcrSessionTransaction;
164 ((JcrSessionTransaction)transaction).commit();
165 }
166
167
168
169
170 @Override
171 public void rollback( ITransaction transaction ) {
172
173
174 }
175
176
177
178
179 @Override
180 public void checkAuthentication( ITransaction transaction ) {
181
182 }
183
184
185
186
187 @Override
188 public void createFolder( ITransaction transaction,
189 String folderUri ) {
190 int ind = folderUri.lastIndexOf('/');
191 String parentUri = folderUri.substring(0, ind + 1);
192 String resourceName = folderUri.substring(ind + 1);
193
194 try {
195 Node parentNode = nodeFor(transaction, parentUri);
196 parentNode.addNode(resourceName, newFolderPrimaryType);
197
198 } catch (RepositoryException re) {
199 throw new WebdavException(re);
200 }
201 }
202
203
204
205
206 @Override
207 public void createResource( ITransaction transaction,
208 String resourceUri ) {
209
210 if (resourceUri.endsWith(".DS_Store")) return;
211
212 int ind = resourceUri.lastIndexOf('/');
213 String parentUri = resourceUri.substring(0, ind + 1);
214 String resourceName = resourceUri.substring(ind + 1);
215
216
217 if (resourceName.startsWith("._")) {
218 OSX_DOUBLE_DATA.put(resourceUri, null);
219 return;
220 }
221
222 try {
223 Node parentNode = nodeFor(transaction, parentUri);
224 Node resourceNode = parentNode.addNode(resourceName, newResourcePrimaryType);
225
226 Node contentNode = resourceNode.addNode(CONTENT_NODE_NAME, newContentPrimaryType);
227 contentNode.setProperty(DATA_PROP_NAME, "");
228 contentNode.setProperty(MODIFIED_PROP_NAME, Calendar.getInstance());
229 contentNode.setProperty(ENCODING_PROP_NAME, "UTF-8");
230 contentNode.setProperty(MIME_TYPE_PROP_NAME, "text/plain");
231
232 } catch (RepositoryException re) {
233 throw new WebdavException(re);
234 }
235
236 }
237
238
239
240
241 @Override
242 public String[] getChildrenNames( ITransaction transaction,
243 String folderUri ) {
244 try {
245 Node node = nodeFor(transaction, folderUri);
246
247 if (isFile(node) || isContent(node)) {
248 return null;
249 }
250
251 List<String> children = new LinkedList<String>();
252 for (NodeIterator iter = node.getNodes(); iter.hasNext();) {
253 children.add(iter.nextNode().getName());
254 }
255
256 return children.toArray(EMPTY_STRING_ARRAY);
257 } catch (RepositoryException re) {
258 throw new WebdavException(re);
259 }
260 }
261
262
263
264
265 @Override
266 public InputStream getResourceContent( ITransaction transaction,
267 String resourceUri ) {
268 try {
269 Node node = nodeFor(transaction, resourceUri);
270
271 if (!isFile(node)) {
272 return null;
273 }
274
275 if (!node.hasNode(CONTENT_NODE_NAME)) {
276 return null;
277 }
278
279 return node.getProperty(CONTENT_NODE_NAME + "/" + DATA_PROP_NAME).getBinary().getStream();
280
281 } catch (RepositoryException re) {
282 throw new WebdavException(re);
283 }
284 }
285
286
287
288
289 @Override
290 public long getResourceLength( ITransaction transaction,
291 String path ) {
292 try {
293 Node node = nodeFor(transaction, path);
294
295 if (!isFile(node)) {
296 return -1;
297 }
298
299 if (!node.hasNode(CONTENT_NODE_NAME)) {
300 return -1;
301 }
302
303 Property contentProp = node.getProperty(CONTENT_NODE_NAME + "/" + DATA_PROP_NAME);
304 long length = contentProp.getLength();
305 if (length != -1) return length;
306
307 String data = contentProp.getString();
308 return data.length();
309
310 } catch (RepositoryException re) {
311 throw new WebdavException(re);
312 }
313 }
314
315
316
317
318 @Override
319 public StoredObject getStoredObject( ITransaction transaction,
320 String uri ) {
321 if (uri.length() == 0) uri = "/";
322
323 StoredObject ob = new StoredObject();
324 try {
325 Node node = nodeFor(transaction, uri);
326
327 if (isContent(node)) {
328 return null;
329 }
330
331 if (!isFile(node)) {
332 ob.setFolder(true);
333 ob.setCreationDate(new Date());
334 ob.setLastModified(new Date());
335 ob.setResourceLength(0);
336
337 } else if (node.hasNode(CONTENT_NODE_NAME)) {
338 Node content = node.getNode(CONTENT_NODE_NAME);
339
340 ob.setFolder(false);
341 Date createDate;
342 if (node.hasProperty(CREATED_PROP_NAME)) {
343 createDate = node.getProperty(CREATED_PROP_NAME).getDate().getTime();
344 } else {
345 createDate = new Date();
346 }
347 ob.setCreationDate(createDate);
348 ob.setLastModified(content.getProperty(MODIFIED_PROP_NAME).getDate().getTime());
349 ob.setResourceLength(content.getProperty(DATA_PROP_NAME).getLength());
350 } else {
351 ob.setNullResource(true);
352 }
353
354 } catch (PathNotFoundException pnfe) {
355 return null;
356 } catch (RepositoryException re) {
357 throw new WebdavException(re);
358 }
359 return ob;
360 }
361
362
363
364
365 @Override
366 public void removeObject( ITransaction transaction,
367 String uri ) {
368 int ind = uri.lastIndexOf('/');
369 String resourceName = uri.substring(ind + 1);
370
371
372 if (resourceName.startsWith("._")) {
373 OSX_DOUBLE_DATA.put(uri, null);
374 return;
375 }
376
377 try {
378 nodeFor(transaction, uri).remove();
379 } catch (PathNotFoundException pnfe) {
380
381 } catch (RepositoryException re) {
382 throw new WebdavException(re);
383 }
384 }
385
386
387
388
389 @Override
390 public long setResourceContent( ITransaction transaction,
391 String resourceUri,
392 InputStream content,
393 String contentType,
394 String characterEncoding ) {
395
396 if (resourceUri.endsWith(".DS_Store")) return 0;
397
398 int ind = resourceUri.lastIndexOf('/');
399 String resourceName = resourceUri.substring(ind + 1);
400
401
402 if (resourceName.startsWith("._")) {
403 try {
404 OSX_DOUBLE_DATA.put(resourceUri, IoUtil.readBytes(content));
405 } catch (IOException e) {
406 throw new RuntimeException(e);
407 }
408 return 0;
409 }
410
411 try {
412 Node node = nodeFor(transaction, resourceUri);
413 if (!isFile(node)) {
414 return -1;
415 }
416
417 Node contentNode;
418 if (node.hasNode(CONTENT_NODE_NAME)) {
419 contentNode = node.getNode(CONTENT_NODE_NAME);
420 } else {
421 contentNode = node.addNode(CONTENT_NODE_NAME, newContentPrimaryType);
422 }
423
424
425 contentNode.setProperty(ENCODING_PROP_NAME, characterEncoding != null ? characterEncoding : "UTF-8");
426 Binary binary = node.getSession().getValueFactory().createBinary(content);
427 contentNode.setProperty(DATA_PROP_NAME, binary);
428 contentNode.setProperty(MODIFIED_PROP_NAME, Calendar.getInstance());
429
430
431
432 if (contentType == null) {
433 contentType = mimeTypeDetector.mimeTypeOf(resourceName, binary.getStream());
434 }
435
436 return contentNode.getProperty(DATA_PROP_NAME).getLength();
437 } catch (RepositoryException re) {
438 throw new WebdavException(re);
439 } catch (IOException ioe) {
440 throw new WebdavException(ioe);
441 } catch (RuntimeException t) {
442 throw t;
443 }
444 }
445
446
447
448
449
450
451 private boolean isContent( Node node ) throws RepositoryException {
452 for (String nodeType : contentPrimaryTypes) {
453 if (node.isNodeType(nodeType)) return true;
454 }
455
456 return false;
457 }
458
459
460
461
462
463
464 private boolean isFile( Node node ) throws RepositoryException {
465 for (String nodeType : filePrimaryTypes) {
466 if (node.isNodeType(nodeType)) return true;
467 }
468
469 return false;
470 }
471
472
473
474
475
476
477
478
479 private final Node nodeFor( ITransaction transaction,
480 String uri ) throws RepositoryException {
481 return ((JcrSessionTransaction)transaction).nodeFor(uri);
482 }
483
484 protected final RequestResolver uriResolver() {
485 return uriResolver;
486 }
487
488
489
490
491
492 class JcrSessionTransaction implements ITransaction {
493
494 private final Principal principal;
495 private final Session session;
496 private final UriResolver uriResolver;
497
498 @SuppressWarnings( "synthetic-access" )
499 JcrSessionTransaction( Principal principal ) {
500 super();
501 this.principal = principal;
502
503 HttpServletRequest request = THREAD_LOCAL_REQUEST.get();
504
505 if (request == null) {
506 throw new WebdavException(WebdavI18n.noStoredRequest.text());
507 }
508
509 try {
510 ResolvedRequest resolvedRequest = uriResolver().resolve(request);
511
512 this.session = RepositoryFactory.getSession(request,
513 resolvedRequest.getRepositoryName(),
514 resolvedRequest.getWorkspaceName());
515 this.uriResolver = resolvedRequest.getUriResolver();
516 assert session != null;
517 } catch (RepositoryException re) {
518 throw new WebdavException(re);
519 }
520 }
521
522
523
524
525 Session session() {
526 return this.session;
527 }
528
529 Node nodeFor( String uri ) throws RepositoryException {
530 String resolvedUri = uriResolver.resolve(uri);
531 Item item = session.getItem(resolvedUri);
532 if (item instanceof Property) {
533 throw new WebdavException();
534 }
535
536 return (Node)item;
537
538 }
539
540
541
542
543 @Override
544 public Principal getPrincipal() {
545 return principal;
546 }
547
548
549
550
551 void commit() {
552 try {
553 this.session.save();
554 } catch (RepositoryException re) {
555 throw new WebdavException(re);
556 }
557 }
558 }
559 }