This project has retired. For details please refer to its Attic page.
ArchivaDavResource xref
View Javadoc
1   package org.apache.archiva.webdav;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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         // TODO: push into locator as well as moving any references out of the resource factory
126         this.repository = repository;
127 
128         // TODO: these should be pushed into the repository layer, along with the physical file operations in this class
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                 // should not occur
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() ) // New File
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             // TODO: a bad deployment shouldn't delete an existing file - do we need to write to a temporary location first?
332             long expectedContentLength = inputContext.getContentLength();
333             long actualContentLength = localFile.length();
334             // length of -1 is given for a chunked request or unknown length, in which case we accept what was uploaded
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() ) // New directory
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                     // Should not occur
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      * Fill the set of properties
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         // set (or reset) fundamental properties
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             // Windows XP support
664             properties.add( new DefaultDavProperty( DavPropertyName.ISCOLLECTION, "1" ) );
665         }
666         else
667         {
668             properties.add( new ResourceType( ResourceType.DEFAULT_RESOURCE ) );
669 
670             // Windows XP support
671             properties.add( new DefaultDavProperty( DavPropertyName.ISCOLLECTION, "0" ) );
672         }
673 
674         // Need to get the ISO8601 date for properties
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 }