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