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