This project has retired. For details please refer to its
Attic page.
ArchivaDavResource xref
1 package org.apache.archiva.webdav;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.archiva.admin.model.beans.ManagedRepository;
23 import org.apache.archiva.metadata.model.facets.AuditEvent;
24 import org.apache.archiva.repository.events.AuditListener;
25 import org.apache.archiva.common.filelock.FileLockException;
26 import org.apache.archiva.common.filelock.FileLockManager;
27 import org.apache.archiva.common.filelock.FileLockTimeoutException;
28 import org.apache.archiva.common.filelock.Lock;
29 import org.apache.archiva.redback.components.taskqueue.TaskQueueException;
30 import org.apache.archiva.scheduler.ArchivaTaskScheduler;
31 import org.apache.archiva.scheduler.repository.model.RepositoryArchivaTaskScheduler;
32 import org.apache.archiva.scheduler.repository.model.RepositoryTask;
33 import org.apache.archiva.webdav.util.IndexWriter;
34 import org.apache.archiva.webdav.util.MimeTypes;
35 import org.apache.commons.io.FileUtils;
36 import org.apache.commons.io.IOUtils;
37 import org.apache.jackrabbit.util.Text;
38 import org.apache.jackrabbit.webdav.DavException;
39 import org.apache.jackrabbit.webdav.DavResource;
40 import org.apache.jackrabbit.webdav.DavResourceFactory;
41 import org.apache.jackrabbit.webdav.DavResourceIterator;
42 import org.apache.jackrabbit.webdav.DavResourceIteratorImpl;
43 import org.apache.jackrabbit.webdav.DavResourceLocator;
44 import org.apache.jackrabbit.webdav.DavServletResponse;
45 import org.apache.jackrabbit.webdav.DavSession;
46 import org.apache.jackrabbit.webdav.MultiStatusResponse;
47 import org.apache.jackrabbit.webdav.io.InputContext;
48 import org.apache.jackrabbit.webdav.io.OutputContext;
49 import org.apache.jackrabbit.webdav.lock.ActiveLock;
50 import org.apache.jackrabbit.webdav.lock.LockInfo;
51 import org.apache.jackrabbit.webdav.lock.LockManager;
52 import org.apache.jackrabbit.webdav.lock.Scope;
53 import org.apache.jackrabbit.webdav.lock.Type;
54 import org.apache.jackrabbit.webdav.property.DavProperty;
55 import org.apache.jackrabbit.webdav.property.DavPropertyName;
56 import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
57 import org.apache.jackrabbit.webdav.property.DavPropertySet;
58 import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
59 import org.apache.jackrabbit.webdav.property.ResourceType;
60 import org.joda.time.DateTime;
61 import org.joda.time.format.DateTimeFormatter;
62 import org.joda.time.format.ISODateTimeFormat;
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
65
66 import javax.servlet.http.HttpServletResponse;
67 import java.io.File;
68 import java.io.IOException;
69 import java.io.InputStream;
70 import java.io.OutputStream;
71 import java.nio.file.Files;
72 import java.util.ArrayList;
73 import java.util.List;
74
75
76
77 public class ArchivaDavResource
78 implements DavResource
79 {
80 public static final String HIDDEN_PATH_PREFIX = ".";
81
82 private final ArchivaDavResourceLocator locator;
83
84 private final DavResourceFactory factory;
85
86 private final File localResource;
87
88 private final String logicalResource;
89
90 private DavPropertySet properties = null;
91
92 private LockManager lockManager;
93
94 private final DavSession session;
95
96 private String remoteAddr;
97
98 private final ManagedRepository repository;
99
100 private final MimeTypes mimeTypes;
101
102 private List<AuditListener> auditListeners;
103
104 private String principal;
105
106 public static final String COMPLIANCE_CLASS = "1, 2";
107
108 private final ArchivaTaskScheduler scheduler;
109
110 private final FileLockManager fileLockManager;
111
112 private Logger log = LoggerFactory.getLogger( ArchivaDavResource.class );
113
114 public ArchivaDavResource( String localResource, String logicalResource, ManagedRepository repository,
115 DavSession session, ArchivaDavResourceLocator locator, DavResourceFactory factory,
116 MimeTypes mimeTypes, List<AuditListener> auditListeners,
117 RepositoryArchivaTaskScheduler scheduler, FileLockManager fileLockManager )
118 {
119 this.localResource = new File( localResource );
120 this.logicalResource = logicalResource;
121 this.locator = locator;
122 this.factory = factory;
123 this.session = session;
124
125
126 this.repository = repository;
127
128
129 this.mimeTypes = mimeTypes;
130 this.auditListeners = auditListeners;
131 this.scheduler = scheduler;
132 this.fileLockManager = fileLockManager;
133 }
134
135 public ArchivaDavResource( String localResource, String logicalResource, ManagedRepository repository,
136 String remoteAddr, String principal, DavSession session,
137 ArchivaDavResourceLocator locator, DavResourceFactory factory, MimeTypes mimeTypes,
138 List<AuditListener> auditListeners, RepositoryArchivaTaskScheduler scheduler,
139 FileLockManager fileLockManager )
140 {
141 this( localResource, logicalResource, repository, session, locator, factory, mimeTypes, auditListeners,
142 scheduler, fileLockManager );
143
144 this.remoteAddr = remoteAddr;
145 this.principal = principal;
146 }
147
148 @Override
149 public String getComplianceClass()
150 {
151 return COMPLIANCE_CLASS;
152 }
153
154 @Override
155 public String getSupportedMethods()
156 {
157 return METHODS;
158 }
159
160 @Override
161 public boolean exists()
162 {
163 return localResource.exists();
164 }
165
166 @Override
167 public boolean isCollection()
168 {
169 return localResource.isDirectory();
170 }
171
172 @Override
173 public String getDisplayName()
174 {
175 String resPath = getResourcePath();
176 return ( resPath != null ) ? Text.getName( resPath ) : resPath;
177 }
178
179 @Override
180 public DavResourceLocator getLocator()
181 {
182 return locator;
183 }
184
185 public File getLocalResource()
186 {
187 return localResource;
188 }
189
190 @Override
191 public String getResourcePath()
192 {
193 return locator.getResourcePath();
194 }
195
196 @Override
197 public String getHref()
198 {
199 return locator.getHref( isCollection() );
200 }
201
202 @Override
203 public long getModificationTime()
204 {
205 return localResource.lastModified();
206 }
207
208 @Override
209 public void spool( OutputContext outputContext )
210 throws IOException
211 {
212 if ( !isCollection() )
213 {
214 outputContext.setContentLength( localResource.length() );
215 outputContext.setContentType( mimeTypes.getMimeType( localResource.getName() ) );
216 }
217
218 try
219 {
220 if ( !isCollection() && outputContext.hasStream() )
221 {
222 Lock lock = fileLockManager.readFileLock( localResource );
223 try (InputStream is = Files.newInputStream( lock.getFile().toPath() ))
224 {
225 IOUtils.copy( is, outputContext.getOutputStream() );
226 }
227 }
228 else if ( outputContext.hasStream() )
229 {
230 IndexWriter writer = new IndexWriter( this, localResource, logicalResource );
231 writer.write( outputContext );
232 }
233 }
234 catch ( FileLockException e )
235 {
236 throw new IOException( e.getMessage(), e );
237 }
238 catch ( FileLockTimeoutException e )
239 {
240 throw new IOException( e.getMessage(), e );
241 }
242 }
243
244 @Override
245 public DavPropertyName[] getPropertyNames()
246 {
247 return getProperties().getPropertyNames();
248 }
249
250 @Override
251 public DavProperty getProperty( DavPropertyName name )
252 {
253 return getProperties().get( name );
254 }
255
256 @Override
257 public DavPropertySet getProperties()
258 {
259 return initProperties();
260 }
261
262 @Override
263 public void setProperty( DavProperty property )
264 throws DavException
265 {
266 }
267
268 @Override
269 public void removeProperty( DavPropertyName propertyName )
270 throws DavException
271 {
272 }
273
274 public MultiStatusResponse alterProperties( DavPropertySet setProperties, DavPropertyNameSet removePropertyNames )
275 throws DavException
276 {
277 return null;
278 }
279
280 @SuppressWarnings("unchecked")
281 @Override
282 public MultiStatusResponse alterProperties( List changeList )
283 throws DavException
284 {
285 return null;
286 }
287
288 @Override
289 public DavResource getCollection()
290 {
291 DavResource parent = null;
292 if ( getResourcePath() != null && !getResourcePath().equals( "/" ) )
293 {
294 String parentPath = Text.getRelativeParent( getResourcePath(), 1 );
295 if ( parentPath.equals( "" ) )
296 {
297 parentPath = "/";
298 }
299 DavResourceLocator parentloc =
300 locator.getFactory().createResourceLocator( locator.getPrefix(), parentPath );
301 try
302 {
303 parent = factory.createResource( parentloc, session );
304 }
305 catch ( DavException e )
306 {
307
308 }
309 }
310 return parent;
311 }
312
313 @Override
314 public void addMember( DavResource resource, InputContext inputContext )
315 throws DavException
316 {
317 File localFile = new File( localResource, resource.getDisplayName() );
318 boolean exists = localFile.exists();
319
320 if ( isCollection() && inputContext.hasStream() )
321 {
322 try (OutputStream stream = Files.newOutputStream( localFile.toPath() ))
323 {
324 IOUtils.copy( inputContext.getInputStream(), stream );
325 }
326 catch ( IOException e )
327 {
328 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
329 }
330
331
332 long expectedContentLength = inputContext.getContentLength();
333 long actualContentLength = localFile.length();
334
335 if ( expectedContentLength >= 0 && expectedContentLength != actualContentLength )
336 {
337 String msg = "Content Header length was " + expectedContentLength + " but was " + actualContentLength;
338 log.debug( "Upload failed: {}", msg );
339
340 FileUtils.deleteQuietly( localFile );
341 throw new DavException( HttpServletResponse.SC_BAD_REQUEST, msg );
342 }
343
344 queueRepositoryTask( localFile );
345
346 log.debug( "File '{}{}(current user '{}')", resource.getDisplayName(),
347 ( exists ? "' modified " : "' created " ), this.principal );
348
349 triggerAuditEvent( resource, exists ? AuditEvent.MODIFY_FILE : AuditEvent.CREATE_FILE );
350 }
351 else if ( !inputContext.hasStream() && isCollection() )
352 {
353 localFile.mkdir();
354
355 log.debug( "Directory '{}' (current user '{}')", resource.getDisplayName(), this.principal );
356
357 triggerAuditEvent( resource, AuditEvent.CREATE_DIR );
358 }
359 else
360 {
361 String msg = "Could not write member " + resource.getResourcePath() + " at " + getResourcePath()
362 + " as this is not a DAV collection";
363 log.debug( msg );
364 throw new DavException( HttpServletResponse.SC_BAD_REQUEST, msg );
365 }
366 }
367
368 @Override
369 public DavResourceIterator getMembers()
370 {
371 List<DavResource> list = new ArrayList<>();
372 if ( exists() && isCollection() )
373 {
374 for ( String item : localResource.list() )
375 {
376 try
377 {
378 if ( !item.startsWith( HIDDEN_PATH_PREFIX ) )
379 {
380 String path = locator.getResourcePath() + '/' + item;
381 DavResourceLocator resourceLocator =
382 locator.getFactory().createResourceLocator( locator.getPrefix(), path );
383 DavResource resource = factory.createResource( resourceLocator, session );
384
385 if ( resource != null )
386 {
387 list.add( resource );
388 }
389 log.debug( "Resource '{}' retrieved by '{}'", item, this.principal );
390 }
391 }
392 catch ( DavException e )
393 {
394
395 }
396 }
397 }
398 return new DavResourceIteratorImpl( list );
399 }
400
401 @Override
402 public void removeMember( DavResource member )
403 throws DavException
404 {
405 File resource = checkDavResourceIsArchivaDavResource( member ).getLocalResource();
406
407 if ( resource.exists() )
408 {
409 try
410 {
411 if ( resource.isDirectory() )
412 {
413 if ( !FileUtils.deleteQuietly( resource ) )
414 {
415 throw new IOException( "Could not remove directory" );
416 }
417
418 triggerAuditEvent( member, AuditEvent.REMOVE_DIR );
419 }
420 else
421 {
422 if ( !resource.delete() )
423 {
424 throw new IOException( "Could not remove file" );
425 }
426
427 triggerAuditEvent( member, AuditEvent.REMOVE_FILE );
428 }
429
430 log.debug( "{}{}' removed (current user '{}')", ( resource.isDirectory() ? "Directory '" : "File '" ),
431 member.getDisplayName(), this.principal );
432
433 }
434 catch ( IOException e )
435 {
436 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR );
437 }
438 }
439 else
440 {
441 throw new DavException( HttpServletResponse.SC_NOT_FOUND );
442 }
443 }
444
445 private void triggerAuditEvent( DavResource member, String action )
446 throws DavException
447 {
448 String path = logicalResource + "/" + member.getDisplayName();
449
450 ArchivaDavResource resource = checkDavResourceIsArchivaDavResource( member );
451 AuditEvent auditEvent = new AuditEvent( locator.getRepositoryId(), resource.principal, path, action );
452 auditEvent.setRemoteIP( resource.remoteAddr );
453
454 for ( AuditListener listener : auditListeners )
455 {
456 listener.auditEvent( auditEvent );
457 }
458 }
459
460 @Override
461 public void move( DavResource destination )
462 throws DavException
463 {
464 if ( !exists() )
465 {
466 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource to copy does not exist." );
467 }
468
469 try
470 {
471 ArchivaDavResource resource = checkDavResourceIsArchivaDavResource( destination );
472 if ( isCollection() )
473 {
474 FileUtils.moveDirectory( getLocalResource(), resource.getLocalResource() );
475
476 triggerAuditEvent( remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.MOVE_DIRECTORY );
477 }
478 else
479 {
480 FileUtils.moveFile( getLocalResource(), resource.getLocalResource() );
481
482 triggerAuditEvent( remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.MOVE_FILE );
483 }
484
485 log.debug( "{}{}' moved to '{}' (current user '{}')", ( isCollection() ? "Directory '" : "File '" ),
486 getLocalResource().getName(), destination, this.principal );
487
488 }
489 catch ( IOException e )
490 {
491 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
492 }
493 }
494
495 @Override
496 public void copy( DavResource destination, boolean shallow )
497 throws DavException
498 {
499 if ( !exists() )
500 {
501 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource to copy does not exist." );
502 }
503
504 if ( shallow && isCollection() )
505 {
506 throw new DavException( DavServletResponse.SC_FORBIDDEN, "Unable to perform shallow copy for collection" );
507 }
508
509 try
510 {
511 ArchivaDavResource resource = checkDavResourceIsArchivaDavResource( destination );
512 if ( isCollection() )
513 {
514 FileUtils.copyDirectory( getLocalResource(), resource.getLocalResource() );
515
516 triggerAuditEvent( remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.COPY_DIRECTORY );
517 }
518 else
519 {
520 FileUtils.copyFile( getLocalResource(), resource.getLocalResource() );
521
522 triggerAuditEvent( remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.COPY_FILE );
523 }
524
525 log.debug( "{}{}' copied to '{}' (current user '{)')", ( isCollection() ? "Directory '" : "File '" ),
526 getLocalResource().getName(), destination, this.principal );
527
528 }
529 catch ( IOException e )
530 {
531 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
532 }
533 }
534
535 @Override
536 public boolean isLockable( Type type, Scope scope )
537 {
538 return Type.WRITE.equals( type ) && Scope.EXCLUSIVE.equals( scope );
539 }
540
541 @Override
542 public boolean hasLock( Type type, Scope scope )
543 {
544 return getLock( type, scope ) != null;
545 }
546
547 @Override
548 public ActiveLock getLock( Type type, Scope scope )
549 {
550 ActiveLock lock = null;
551 if ( exists() && Type.WRITE.equals( type ) && Scope.EXCLUSIVE.equals( scope ) )
552 {
553 lock = lockManager.getLock( type, scope, this );
554 }
555 return lock;
556 }
557
558 @Override
559 public ActiveLock[] getLocks()
560 {
561 ActiveLock writeLock = getLock( Type.WRITE, Scope.EXCLUSIVE );
562 return ( writeLock != null ) ? new ActiveLock[]{ writeLock } : new ActiveLock[0];
563 }
564
565 @Override
566 public ActiveLock lock( LockInfo lockInfo )
567 throws DavException
568 {
569 ActiveLock lock = null;
570 if ( isLockable( lockInfo.getType(), lockInfo.getScope() ) )
571 {
572 lock = lockManager.createLock( lockInfo, this );
573 }
574 else
575 {
576 throw new DavException( DavServletResponse.SC_PRECONDITION_FAILED, "Unsupported lock type or scope." );
577 }
578 return lock;
579 }
580
581 @Override
582 public ActiveLock refreshLock( LockInfo lockInfo, String lockToken )
583 throws DavException
584 {
585 if ( !exists() )
586 {
587 throw new DavException( DavServletResponse.SC_NOT_FOUND );
588 }
589 ActiveLock lock = getLock( lockInfo.getType(), lockInfo.getScope() );
590 if ( lock == null )
591 {
592 throw new DavException( DavServletResponse.SC_PRECONDITION_FAILED,
593 "No lock with the given type/scope present on resource " + getResourcePath() );
594 }
595
596 lock = lockManager.refreshLock( lockInfo, lockToken, this );
597
598 return lock;
599 }
600
601 @Override
602 public void unlock( String lockToken )
603 throws DavException
604 {
605 ActiveLock lock = getLock( Type.WRITE, Scope.EXCLUSIVE );
606 if ( lock == null )
607 {
608 throw new DavException( HttpServletResponse.SC_PRECONDITION_FAILED );
609 }
610 else if ( lock.isLockedByToken( lockToken ) )
611 {
612 lockManager.releaseLock( lockToken, this );
613 }
614 else
615 {
616 throw new DavException( DavServletResponse.SC_LOCKED );
617 }
618 }
619
620 @Override
621 public void addLockManager( LockManager lockManager )
622 {
623 this.lockManager = lockManager;
624 }
625
626 @Override
627 public DavResourceFactory getFactory()
628 {
629 return factory;
630 }
631
632 @Override
633 public DavSession getSession()
634 {
635 return session;
636 }
637
638
639
640
641 protected DavPropertySet initProperties()
642 {
643 if ( !exists() )
644 {
645 properties = new DavPropertySet();
646 }
647
648 if ( properties != null )
649 {
650 return properties;
651 }
652
653 DavPropertySet properties = new DavPropertySet();
654
655
656 if ( getDisplayName() != null )
657 {
658 properties.add( new DefaultDavProperty( DavPropertyName.DISPLAYNAME, getDisplayName() ) );
659 }
660 if ( isCollection() )
661 {
662 properties.add( new ResourceType( ResourceType.COLLECTION ) );
663
664 properties.add( new DefaultDavProperty( DavPropertyName.ISCOLLECTION, "1" ) );
665 }
666 else
667 {
668 properties.add( new ResourceType( ResourceType.DEFAULT_RESOURCE ) );
669
670
671 properties.add( new DefaultDavProperty( DavPropertyName.ISCOLLECTION, "0" ) );
672 }
673
674
675 DateTime dt = new DateTime( localResource.lastModified() );
676 DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
677 String modifiedDate = fmt.print( dt );
678
679 properties.add( new DefaultDavProperty( DavPropertyName.GETLASTMODIFIED, modifiedDate ) );
680
681 properties.add( new DefaultDavProperty( DavPropertyName.CREATIONDATE, modifiedDate ) );
682
683 properties.add( new DefaultDavProperty( DavPropertyName.GETCONTENTLENGTH, localResource.length() ) );
684
685 this.properties = properties;
686
687 return properties;
688 }
689
690 private ArchivaDavResource checkDavResourceIsArchivaDavResource( DavResource resource )
691 throws DavException
692 {
693 if ( !( resource instanceof ArchivaDavResource ) )
694 {
695 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
696 "DavResource is not instance of ArchivaDavResource" );
697 }
698 return (ArchivaDavResource) resource;
699 }
700
701 private void triggerAuditEvent( String remoteIP, String repositoryId, String resource, String action )
702 {
703 AuditEvent event = new AuditEvent( repositoryId, principal, resource, action );
704 event.setRemoteIP( remoteIP );
705
706 for ( AuditListener listener : auditListeners )
707 {
708 listener.auditEvent( event );
709 }
710 }
711
712 private void queueRepositoryTask( File localFile )
713 {
714 RepositoryTask task = new RepositoryTask();
715 task.setRepositoryId( repository.getId() );
716 task.setResourceFile( localFile );
717 task.setUpdateRelatedArtifacts( false );
718 task.setScanAll( false );
719
720 try
721 {
722 scheduler.queueTask( task );
723 }
724 catch ( TaskQueueException e )
725 {
726 log.error( "Unable to queue repository task to execute consumers on resource file ['" + localFile.getName()
727 + "']." );
728 }
729 }
730 }