]> source.dussan.org Git - archiva.git/blob
1de35015b7b1ab0a14638098b0a79858a54907f7
[archiva.git] /
1 package org.apache.maven.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 java.io.File;
23 import java.io.FileInputStream;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.List;
28
29 import javax.servlet.http.HttpServletResponse;
30
31 import org.apache.commons.io.FileUtils;
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.apache.maven.archiva.common.ArchivaException;
57 import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
58 import org.apache.maven.archiva.repository.audit.AuditEvent;
59 import org.apache.maven.archiva.repository.audit.AuditListener;
60 import org.apache.maven.archiva.repository.scanner.RepositoryContentConsumers;
61 import org.apache.maven.archiva.scheduled.ArchivaTaskScheduler;
62 import org.apache.maven.archiva.scheduled.DefaultArchivaTaskScheduler;
63 import org.apache.maven.archiva.scheduled.executors.ArchivaRepositoryScanningTaskExecutor;
64 import org.apache.maven.archiva.scheduled.tasks.ArchivaTask;
65 import org.apache.maven.archiva.scheduled.tasks.RepositoryTask;
66 import org.apache.maven.archiva.webdav.util.IndexWriter;
67 import org.apache.maven.archiva.webdav.util.MimeTypes;
68 import org.codehaus.plexus.taskqueue.TaskQueueException;
69 import org.codehaus.plexus.taskqueue.execution.TaskExecutor;
70 import org.joda.time.DateTime;
71 import org.joda.time.format.DateTimeFormatter;
72 import org.joda.time.format.ISODateTimeFormat;
73 import org.slf4j.Logger;
74 import org.slf4j.LoggerFactory;
75
76 /**
77  */
78 public class ArchivaDavResource
79     implements DavResource
80 {
81     public static final String HIDDEN_PATH_PREFIX = ".";
82
83     private final ArchivaDavResourceLocator locator;
84
85     private final DavResourceFactory factory;
86
87     private final File localResource;
88
89     private final String logicalResource;
90
91     private DavPropertySet properties = null;
92
93     private LockManager lockManager;
94     
95     private final DavSession session;
96     
97     private String remoteAddr;
98
99     private final ManagedRepositoryConfiguration repository;
100
101     private final RepositoryContentConsumers consumers;
102
103     private final MimeTypes mimeTypes;
104
105     private List<AuditListener> auditListeners;
106
107     private String principal;
108     
109         public static final String COMPLIANCE_CLASS = "1, 2";
110         
111         private ArchivaTaskScheduler scheduler;
112         
113         private ArchivaRepositoryScanningTaskExecutor taskExecutor;
114         
115         private Logger log = LoggerFactory.getLogger( ArchivaDavResource.class );
116
117     public ArchivaDavResource( String localResource, String logicalResource, ManagedRepositoryConfiguration repository,
118                                DavSession session, ArchivaDavResourceLocator locator, DavResourceFactory factory,
119                                MimeTypes mimeTypes, List<AuditListener> auditListeners,
120                                RepositoryContentConsumers consumers, ArchivaTaskScheduler scheduler, TaskExecutor taskExecutor )
121     {
122         this.localResource = new File( localResource ); 
123         this.logicalResource = logicalResource;
124         this.locator = locator;
125         this.factory = factory;
126         this.session = session;
127         
128         // TODO: push into locator as well as moving any references out of the resource factory
129         this.repository = repository;
130         
131         // TODO: these should be pushed into the repository layer, along with the physical file operations in this class
132         this.mimeTypes = mimeTypes;
133         this.consumers = consumers;
134         this.auditListeners = auditListeners;
135         this.scheduler = scheduler;
136         this.taskExecutor = ( ArchivaRepositoryScanningTaskExecutor ) taskExecutor;
137     }
138
139     public ArchivaDavResource( String localResource, String logicalResource, ManagedRepositoryConfiguration repository,
140                                String remoteAddr, String principal, DavSession session, ArchivaDavResourceLocator locator,
141                                DavResourceFactory factory, MimeTypes mimeTypes, List<AuditListener> auditListeners,
142                                RepositoryContentConsumers consumers, ArchivaTaskScheduler scheduler, TaskExecutor taskExecutor )
143     {
144         this( localResource, logicalResource, repository, session, locator, factory, mimeTypes, auditListeners,
145               consumers, scheduler, taskExecutor );
146
147         this.remoteAddr = remoteAddr;
148         this.principal = principal;
149     }
150
151     public String getComplianceClass()
152     {
153         return COMPLIANCE_CLASS;
154     }
155
156     public String getSupportedMethods()
157     {
158         return METHODS;
159     }
160
161     public boolean exists()
162     {
163         return localResource.exists();
164     }
165
166     public boolean isCollection()
167     {
168         return localResource.isDirectory();
169     }
170
171     public String getDisplayName()
172     {
173         String resPath = getResourcePath();
174         return ( resPath != null ) ? Text.getName( resPath ) : resPath;
175     }
176
177     public DavResourceLocator getLocator()
178     {
179         return locator;
180     }
181
182     public File getLocalResource()
183     {
184         return localResource;
185     }
186
187     public String getResourcePath()
188     {
189         return locator.getResourcePath();
190     }
191
192     public String getHref()
193     {
194         return locator.getHref( isCollection() );
195     }
196
197     public long getModificationTime()
198     {
199         return localResource.lastModified();
200     }
201
202     public void spool( OutputContext outputContext )
203         throws IOException
204     {
205         if ( !isCollection())
206         {
207             outputContext.setContentLength( localResource.length() );
208             outputContext.setContentType( mimeTypes.getMimeType( localResource.getName() ) );
209         }
210         
211         if ( !isCollection() && outputContext.hasStream() )
212         {
213             FileInputStream is = null;
214             try
215             {
216                 // Write content to stream
217                 is = new FileInputStream( localResource );
218                 IOUtils.copy( is, outputContext.getOutputStream() );
219             }
220             finally
221             {
222                 IOUtils.closeQuietly( is );
223             }
224         }
225         else if (outputContext.hasStream())
226         {
227             IndexWriter writer = new IndexWriter( this, localResource, logicalResource );
228             writer.write( outputContext );
229         }
230     }
231
232     public DavPropertyName[] getPropertyNames()
233     {
234         return getProperties().getPropertyNames();
235     }
236
237     public DavProperty getProperty( DavPropertyName name )
238     {
239         return getProperties().get( name );
240     }
241
242     public DavPropertySet getProperties()
243     {
244         return initProperties();
245     }
246
247     public void setProperty( DavProperty property )
248         throws DavException
249     {
250     }
251
252     public void removeProperty( DavPropertyName propertyName )
253         throws DavException
254     {
255     }
256
257     public MultiStatusResponse alterProperties( DavPropertySet setProperties, DavPropertyNameSet removePropertyNames )
258         throws DavException
259     {
260         return null;
261     }
262
263     @SuppressWarnings("unchecked")
264     public MultiStatusResponse alterProperties( List changeList )
265         throws DavException
266     {
267         return null;
268     }
269
270     public DavResource getCollection()
271     {
272         DavResource parent = null;
273         if ( getResourcePath() != null && !getResourcePath().equals( "/" ) )
274         {
275             String parentPath = Text.getRelativeParent( getResourcePath(), 1 );
276             if ( parentPath.equals( "" ) )
277             {
278                 parentPath = "/";
279             }
280             DavResourceLocator parentloc = locator.getFactory().createResourceLocator( locator.getPrefix(), parentPath );
281             try
282             {
283                 parent = factory.createResource( parentloc, session );
284             }
285             catch ( DavException e )
286             {
287                 // should not occur
288             }
289         }
290         return parent;
291     }
292
293     public void addMember( DavResource resource, InputContext inputContext )
294         throws DavException
295     {
296         File localFile = new File( localResource, resource.getDisplayName() );
297         boolean exists = localFile.exists();
298
299         if ( isCollection() && inputContext.hasStream() ) // New File
300         {
301             FileOutputStream stream = null;
302             try
303             {
304                 stream = new FileOutputStream( localFile );
305                 IOUtils.copy( inputContext.getInputStream(), stream );
306             }
307             catch ( IOException e )
308             {
309                 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
310             }
311             finally
312             {
313                 IOUtils.closeQuietly( stream );
314             }
315             
316             if ( inputContext.getContentLength() != localFile.length() )
317             {
318                 FileUtils.deleteQuietly( localFile );
319                 
320                 throw new DavException( HttpServletResponse.SC_BAD_REQUEST, "Content Header length was " +
321                     inputContext.getContentLength() + " but was " + localFile.length() );
322             }
323             
324             executeConsumers( localFile );            
325             
326             triggerAuditEvent( resource, exists ? AuditEvent.MODIFY_FILE : AuditEvent.CREATE_FILE );
327         }
328         else if ( !inputContext.hasStream() && isCollection() ) // New directory
329         {
330             localFile.mkdir();
331             
332             triggerAuditEvent( resource, AuditEvent.CREATE_DIR );
333         }
334         else
335         {
336             throw new DavException( HttpServletResponse.SC_BAD_REQUEST, "Could not write member " +
337                 resource.getResourcePath() + " at " + getResourcePath() );
338         }
339     }
340
341     public DavResourceIterator getMembers()
342     {
343         List<DavResource> list = new ArrayList<DavResource>();
344         if ( exists() && isCollection() )
345         {
346             for ( String item : localResource.list() )
347             {
348                 try
349                 {
350                     if ( !item.startsWith( HIDDEN_PATH_PREFIX ) )
351                     {
352                         String path = locator.getResourcePath() + '/' + item;
353                         DavResourceLocator resourceLocator =
354                             locator.getFactory().createResourceLocator( locator.getPrefix(), path );
355                         DavResource resource = factory.createResource( resourceLocator, session );
356                         if ( resource != null )
357                         {
358                             list.add( resource );
359                         }
360                     }
361                 }
362                 catch ( DavException e )
363                 {
364                     // Should not occur
365                 }
366             }
367         }
368         return new DavResourceIteratorImpl( list );
369     }
370
371     public void removeMember( DavResource member )
372         throws DavException
373     {
374         File resource = checkDavResourceIsArchivaDavResource( member ).getLocalResource();
375         
376         if ( resource.exists() )
377         {
378             try
379             {
380                 if ( resource.isDirectory() )
381                 {
382                     FileUtils.deleteDirectory( resource );
383
384                     triggerAuditEvent( member, AuditEvent.REMOVE_DIR );
385                 }
386                 else
387                 {
388                     if ( !resource.delete() )
389                     {
390                         throw new IOException( "Could not remove file" );
391                     }
392
393                     triggerAuditEvent( member, AuditEvent.REMOVE_FILE );
394                 }
395             }
396             catch ( IOException e )
397             {
398                 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR );
399             }
400         }
401         else
402         {
403             throw new DavException( HttpServletResponse.SC_NOT_FOUND );
404         }
405     }
406
407     private void triggerAuditEvent( DavResource member, String event ) throws DavException
408     {
409         String path = logicalResource + "/" + member.getDisplayName();
410         
411         triggerAuditEvent( checkDavResourceIsArchivaDavResource( member ).remoteAddr, locator.getRepositoryId(), path,
412                            event );
413     }
414
415     public void move( DavResource destination )
416         throws DavException
417     {
418         if ( !exists() )
419         {
420             throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource to copy does not exist." );
421         }
422
423         try
424         {
425             ArchivaDavResource resource = checkDavResourceIsArchivaDavResource( destination );
426             if ( isCollection() )
427             {
428                 FileUtils.moveDirectory( getLocalResource(), resource.getLocalResource() );
429
430                 triggerAuditEvent( remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.MOVE_DIRECTORY );
431             }
432             else
433             {
434                 FileUtils.moveFile( getLocalResource(), resource.getLocalResource() );
435
436                 triggerAuditEvent( remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.MOVE_FILE );
437             }
438         }
439         catch ( IOException e )
440         {
441             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
442         }
443     }
444
445     public void copy( DavResource destination, boolean shallow )
446         throws DavException
447     {
448         if ( !exists() )
449         {
450             throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource to copy does not exist." );
451         }
452
453         if ( shallow && isCollection() )
454         {
455             throw new DavException( DavServletResponse.SC_FORBIDDEN, "Unable to perform shallow copy for collection" );
456         }
457
458         try
459         {
460             ArchivaDavResource resource = checkDavResourceIsArchivaDavResource( destination );
461             if ( isCollection() )
462             {
463                 FileUtils.copyDirectory( getLocalResource(), resource.getLocalResource() );
464
465                 triggerAuditEvent( remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.COPY_DIRECTORY );
466             }
467             else
468             {
469                 FileUtils.copyFile( getLocalResource(), resource.getLocalResource() );
470
471                 triggerAuditEvent( remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.COPY_FILE );
472             }
473         }
474         catch ( IOException e )
475         {
476             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
477         }
478     }
479
480     public boolean isLockable( Type type, Scope scope )
481     {
482         return Type.WRITE.equals(type) && Scope.EXCLUSIVE.equals(scope);
483     }
484
485     public boolean hasLock( Type type, Scope scope )
486     {
487         return getLock(type, scope) != null;
488     }
489
490     public ActiveLock getLock( Type type, Scope scope )
491     {
492         ActiveLock lock = null;
493         if (exists() && Type.WRITE.equals(type) && Scope.EXCLUSIVE.equals(scope)) 
494         {
495             lock = lockManager.getLock(type, scope, this);
496         }
497         return lock;
498     }
499
500     public ActiveLock[] getLocks()
501     {
502         ActiveLock writeLock = getLock(Type.WRITE, Scope.EXCLUSIVE);
503         return (writeLock != null) ? new ActiveLock[]{writeLock} : new ActiveLock[0];
504     }
505
506     public ActiveLock lock( LockInfo lockInfo )
507         throws DavException
508     {
509         ActiveLock lock = null;
510         if (isLockable(lockInfo.getType(), lockInfo.getScope())) 
511         {
512             lock = lockManager.createLock(lockInfo, this);
513         }
514         else 
515         {
516             throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED, "Unsupported lock type or scope.");
517         }
518         return lock;
519     }
520
521     public ActiveLock refreshLock( LockInfo lockInfo, String lockToken )
522         throws DavException
523     {
524         if (!exists()) {
525             throw new DavException(DavServletResponse.SC_NOT_FOUND);
526         }
527         ActiveLock lock = getLock(lockInfo.getType(), lockInfo.getScope());
528         if (lock == null) {
529             throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED, "No lock with the given type/scope present on resource " + getResourcePath());
530         }
531
532         lock = lockManager.refreshLock(lockInfo, lockToken, this);
533
534         return lock;
535     }
536
537     public void unlock( String lockToken )
538         throws DavException
539     {
540         ActiveLock lock = getLock(Type.WRITE, Scope.EXCLUSIVE);
541         if (lock == null)
542         {
543             throw new DavException(HttpServletResponse.SC_PRECONDITION_FAILED);
544         }
545         else if (lock.isLockedByToken(lockToken))
546         {
547             lockManager.releaseLock(lockToken, this);
548         }
549         else
550         {
551             throw new DavException(DavServletResponse.SC_LOCKED);
552         }
553     }
554
555     public void addLockManager( LockManager lockManager )
556     {
557         this.lockManager = lockManager;
558     }
559
560     public DavResourceFactory getFactory()
561     {
562         return factory;
563     }
564
565     public DavSession getSession()
566     {
567         return session;
568     }
569
570     /**
571      * Fill the set of properties
572      */
573     protected DavPropertySet initProperties()
574     {
575         if ( !exists() )
576         {
577             properties = new DavPropertySet();
578         }
579         
580         if ( properties != null )
581         {
582             return properties;
583         }
584
585         DavPropertySet properties = new DavPropertySet();
586         
587         // set (or reset) fundamental properties
588         if ( getDisplayName() != null )
589         {
590             properties.add( new DefaultDavProperty( DavPropertyName.DISPLAYNAME, getDisplayName() ) );
591         }
592         if ( isCollection() )
593         {
594             properties.add( new ResourceType( ResourceType.COLLECTION ) );
595             // Windows XP support
596             properties.add( new DefaultDavProperty( DavPropertyName.ISCOLLECTION, "1" ) );
597         }
598         else
599         {
600             properties.add( new ResourceType( ResourceType.DEFAULT_RESOURCE ) );
601
602             // Windows XP support
603             properties.add( new DefaultDavProperty( DavPropertyName.ISCOLLECTION, "0" ) );
604         }
605
606         // Need to get the ISO8601 date for properties
607         DateTime dt = new DateTime( localResource.lastModified() );
608         DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
609         String modifiedDate = fmt.print( dt );
610
611         properties.add( new DefaultDavProperty( DavPropertyName.GETLASTMODIFIED, modifiedDate ) );
612
613         properties.add( new DefaultDavProperty( DavPropertyName.CREATIONDATE, modifiedDate ) );
614
615         properties.add( new DefaultDavProperty( DavPropertyName.GETCONTENTLENGTH, localResource.length() ) );
616         
617         this.properties = properties;
618         
619         return properties;
620     }
621
622     private ArchivaDavResource checkDavResourceIsArchivaDavResource( DavResource resource )
623         throws DavException
624     {
625         if ( !( resource instanceof ArchivaDavResource ) )
626         {
627             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
628                                     "DavResource is not instance of ArchivaDavResource" );
629         }
630         return (ArchivaDavResource) resource;
631     }
632
633     private void triggerAuditEvent( String remoteIP, String repositoryId, String resource, String action )
634     {
635         AuditEvent event = new AuditEvent( repositoryId, principal, resource, action );
636         event.setRemoteIP( remoteIP );
637
638         for ( AuditListener listener : auditListeners )
639         {
640             listener.auditEvent( event );
641         }
642     }
643     
644     private void executeConsumers( File localFile )
645     {
646         try
647         {
648             RepositoryTask currentTaskInExecution = ( RepositoryTask ) taskExecutor.getCurrentTaskInExecution();
649             if( currentTaskInExecution != null || scheduler.isProcessingAnyRepositoryTask() )
650             {   
651                 // check if the repository is already queued to be scanned
652                 if( scheduler.isProcessingRepositoryTaskWithName( DefaultArchivaTaskScheduler.REPOSITORY_JOB + ":" + repository.getId() )
653                         || scheduler.isProcessingRepositoryTaskWithName( DefaultArchivaTaskScheduler.REPOSITORY_JOB + ":" + repository.getId() + ":" + localFile.getName() ) )
654                 {
655                     // no need to execute the consumers since repo is already queued
656                     return;
657                 }
658                 else
659                 {
660                     // schedule the task
661                     RepositoryTask task = new RepositoryTask();
662                     task.setRepositoryId( repository.getId() );
663                     task.setName( DefaultArchivaTaskScheduler.REPOSITORY_JOB + ":" + repository.getId() + ":" + localFile.getName() );
664                     task.setQueuePolicy( ArchivaTask.QUEUE_POLICY_WAIT );
665                     task.setResourceFile( localFile );
666                     
667                     try
668                     {
669                         scheduler.queueRepositoryTask( task );
670                     }
671                     catch ( TaskQueueException e )
672                     {
673                         log.error( "Unable to queue repository task to execute consumers on resource file ['" +
674                             localFile.getName() + "']." );
675                     }
676                 }
677             }
678             else
679             {
680                 // Just-in-time update of the index and database by executing the consumers for this artifact
681                 consumers.executeConsumers( repository, localFile );
682             }
683         }
684         catch ( ArchivaException e )
685         {
686             log.error( "Unable to queue repository task to execute consumers on resource file ['" +
687                        localFile.getName() + "']." );
688         }
689     }
690 }