]> source.dussan.org Git - archiva.git/blob
4c490ab936e1eb1c06628d5f1a40111b8d78771c
[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 com.opensymphony.xwork.ActionContext;
23 import org.apache.jackrabbit.webdav.*;
24 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
25 import org.apache.maven.archiva.repository.RepositoryNotFoundException;
26 import org.apache.maven.archiva.repository.RepositoryException;
27 import org.apache.maven.archiva.repository.RepositoryContentFactory;
28 import org.apache.maven.archiva.repository.layout.LayoutException;
29 import org.apache.maven.archiva.repository.content.RepositoryRequest;
30 import org.apache.maven.archiva.repository.audit.AuditListener;
31 import org.apache.maven.archiva.repository.audit.Auditable;
32 import org.apache.maven.archiva.repository.audit.AuditEvent;
33 import org.apache.maven.archiva.repository.metadata.MetadataTools;
34 import org.apache.maven.archiva.repository.metadata.RepositoryMetadataException;
35 import org.apache.maven.archiva.webdav.util.WebdavMethodUtil;
36 import org.apache.maven.archiva.webdav.util.MimeTypes;
37 import org.apache.maven.archiva.webdav.util.RepositoryPathUtil;
38 import org.apache.maven.archiva.proxy.RepositoryProxyConnectors;
39 import org.apache.maven.archiva.common.utils.PathUtil;
40 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
41 import org.apache.maven.archiva.configuration.RepositoryGroupConfiguration;
42 import org.apache.maven.archiva.model.ArtifactReference;
43 import org.apache.maven.archiva.model.ProjectReference;
44 import org.apache.maven.archiva.model.VersionedReference;
45 import org.apache.maven.archiva.policies.ProxyDownloadException;
46 import org.apache.maven.archiva.security.ArchivaXworkUser;
47 import org.apache.maven.model.DistributionManagement;
48 import org.apache.maven.model.Model;
49 import org.apache.maven.model.Relocation;
50 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
51 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 import javax.servlet.http.HttpServletResponse;
56 import java.util.ArrayList;
57 import java.util.List;
58 import java.io.*;
59
60 /**
61  * @author <a href="mailto:james@atlassian.com">James William Dumay</a>
62  * @plexus.component role="org.apache.maven.archiva.webdav.ArchivaDavResourceFactory"
63  */
64 public class ArchivaDavResourceFactory implements DavResourceFactory, Auditable
65 {
66     private Logger log = LoggerFactory.getLogger(ArchivaDavResourceFactory.class);
67
68     /**
69      * @plexus.requirement role="org.apache.maven.archiva.repository.audit.AuditListener"
70      */
71     private List<AuditListener> auditListeners = new ArrayList<AuditListener>();
72
73     /**
74      * @plexus.requirement
75      */
76     private RepositoryContentFactory repositoryFactory;
77
78     /**
79      * @plexus.requirement
80      */
81     private RepositoryRequest repositoryRequest;
82
83     /**
84      * @plexus.requirement role-hint="default"
85      */
86     private RepositoryProxyConnectors connectors;
87
88     /**
89      * @plexus.requirement
90      */
91     private MetadataTools metadataTools;
92
93     /**
94      * @plexus.requirement
95      */
96     private MimeTypes mimeTypes;
97     
98         
99     /**
100      * @plexus.requirement
101      */
102     private ArchivaConfiguration archivaConfiguration;
103
104     public DavResource createResource(final DavResourceLocator locator, final DavServletRequest request, final DavServletResponse response) throws DavException
105     {   
106         checkLocatorIsInstanceOfRepositoryLocator(locator);
107         ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator)locator;
108         
109         RepositoryGroupConfiguration repoGroupConfig = archivaConfiguration.getConfiguration()
110                     .getRepositoryGroupsAsMap().get( ( (RepositoryLocator) locator).getRepositoryId() );
111                 
112         List<String> repositories = new ArrayList<String>();
113         
114         if ( repoGroupConfig != null )
115         {
116             if ( RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ).equals( "/" )
117                             || WebdavMethodUtil.isWriteMethod( request.getMethod() ) )
118             {                
119                 throw new DavException( HttpServletResponse.SC_BAD_REQUEST, "Bad request to repository group <"
120                                         + repoGroupConfig.getId() + ">" );
121             }
122             repositories.addAll( repoGroupConfig.getRepositories() );
123         }
124         else
125         {            
126             repositories.add( ( (RepositoryLocator) locator).getRepositoryId() );
127         }
128
129         DavResource resource = null;
130         DavException e = null;
131         
132         for ( String repositoryId : repositories )
133         {   
134             ManagedRepositoryContent managedRepository = null;
135             
136             try
137             {
138                 managedRepository = getManagedRepository( repositoryId );                
139             }
140             catch ( DavException de )
141             {                
142                 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Invalid managed repository <" + repositoryId
143                         + ">" );
144             }
145              
146             if (!locator.getResourcePath().startsWith(ArchivaDavResource.HIDDEN_PATH_PREFIX))
147             {   
148                 if (managedRepository != null)
149                 {
150                     LogicalResource logicalResource = new LogicalResource(RepositoryPathUtil.getLogicalResource(locator.getResourcePath()));
151                     
152                     boolean isGet = WebdavMethodUtil.isReadMethod( request.getMethod() );
153                     boolean isPut = WebdavMethodUtil.isWriteMethod( request.getMethod() );
154
155                     if (isGet)
156                     {
157                         resource = doGet(managedRepository, request, archivaLocator, logicalResource);
158                     }
159
160                     if (isPut)
161                     {
162                         resource = doPut(managedRepository, request, archivaLocator, logicalResource);
163                     }
164                 }
165                 else
166                 {                    
167                     e = new DavException(HttpServletResponse.SC_NOT_FOUND, "Repository does not exist");
168                 }
169                 
170                 if (resource == null)
171                 {                 
172                     e = new DavException(HttpServletResponse.SC_NOT_FOUND, "Repository does not exist");
173                 }
174                 else
175                 {
176                     setHeaders(locator, response);
177                   
178                     //compatibility with MRM-440 to ensure browsing the repository works ok
179                     if (resource.isCollection() && !resource.getLocator().getResourcePath().endsWith("/"))
180                     {                        
181                         throw new BrowserRedirectException(resource.getHref());
182                     }
183                     
184                     return resource;
185                 }                
186             }
187         }        
188         
189         throw e;
190     }
191
192     public DavResource createResource(final DavResourceLocator locator, final DavSession davSession) throws DavException
193     {
194         checkLocatorIsInstanceOfRepositoryLocator(locator);
195         ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator)locator;
196
197         DavResource resource = null;
198         if (!locator.getResourcePath().startsWith(ArchivaDavResource.HIDDEN_PATH_PREFIX))
199         {
200             ManagedRepositoryContent managedRepository = getManagedRepository(archivaLocator.getRepositoryId());
201             String logicalResource = RepositoryPathUtil.getLogicalResource(locator.getResourcePath());
202             File resourceFile = new File ( managedRepository.getRepoRoot(), logicalResource);
203             resource = new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource, mimeTypes, archivaLocator, this);                     
204         }
205         return resource;
206     }
207
208     private DavResource doGet(ManagedRepositoryContent managedRepository, DavServletRequest request, ArchivaDavResourceLocator locator, LogicalResource logicalResource) throws DavException
209     {
210         File resourceFile = new File ( managedRepository.getRepoRoot(), logicalResource.getPath());
211         ArchivaDavResource resource = new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator, this);
212
213         if ( !resource.isCollection() )
214         {
215             // At this point the incoming request can either be in default or
216             // legacy layout format.
217             boolean fromProxy = fetchContentFromProxies(managedRepository, request, logicalResource );
218
219             boolean previouslyExisted = resourceFile.exists();
220
221             try
222             {
223                 // Perform an adjustment of the resource to the managed
224                 // repository expected path.
225                 String localResourcePath = repositoryRequest.toNativePath( logicalResource.getPath(), managedRepository );
226                 resourceFile = new File( managedRepository.getRepoRoot(), localResourcePath );
227             }
228             catch ( LayoutException e )
229             {
230                 if ( previouslyExisted )
231                 {
232                     return resource;
233                 }
234                 throw new DavException(HttpServletResponse.SC_NOT_FOUND, e);
235             }
236
237             // Attempt to fetch the resource from any defined proxy.
238             if ( fromProxy )
239             {
240                 processAuditEvents(request, locator.getWorkspaceName(), logicalResource.getPath(), previouslyExisted, resourceFile, " (proxied)");
241             }
242             resource = new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator, this);
243             
244             if ( !resourceFile.exists() )
245             {
246                 resource = null;
247             }
248         }
249         return resource;
250     }
251
252     private DavResource doPut(ManagedRepositoryContent managedRepository, DavServletRequest request, ArchivaDavResourceLocator locator, LogicalResource logicalResource) throws DavException
253     {
254         /*
255          * Create parent directories that don't exist when writing a file
256          * This actually makes this implementation not compliant to the
257          * WebDAV RFC - but we have enough knowledge about how the
258          * collection is being used to do this reasonably and some versions
259          * of Maven's WebDAV don't correctly create the collections
260          * themselves.
261          */
262
263         File rootDirectory = new File(managedRepository.getRepoRoot());
264         File destDir = new File( rootDirectory, logicalResource.getPath() ).getParentFile();
265         if ( !destDir.exists() )
266         {
267             destDir.mkdirs();
268             String relPath = PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
269             triggerAuditEvent(request, logicalResource.getPath(), relPath, AuditEvent.CREATE_DIR );
270         }
271
272         File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
273
274         boolean previouslyExisted = resourceFile.exists();
275
276         processAuditEvents(request, locator.getRepositoryId(), logicalResource.getPath(), previouslyExisted, resourceFile, null );
277
278         return new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator, this);
279     }
280
281     private boolean fetchContentFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request, LogicalResource resource )
282         throws DavException
283     {
284         if ( repositoryRequest.isSupportFile( resource.getPath() ) )
285         {
286             // Checksums are fetched with artifact / metadata.
287
288             // Need to adjust the path for the checksum resource.
289             return false;
290         }
291
292         // Is it a Metadata resource?
293         if ( repositoryRequest.isDefault( resource.getPath() ) && repositoryRequest.isMetadata( resource.getPath() ) )
294         {
295             return fetchMetadataFromProxies(managedRepository, request, resource );
296         }
297
298         // Not any of the above? Then it's gotta be an artifact reference.
299         try
300         {
301             // Get the artifact reference in a layout neutral way.
302             ArtifactReference artifact = repositoryRequest.toArtifactReference( resource.getPath() );
303
304             if ( artifact != null )
305             {
306                 applyServerSideRelocation(managedRepository, artifact );
307
308                 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
309
310                 resource.setPath( managedRepository.toPath( artifact ) );
311
312                 return ( proxiedFile != null );
313             }
314         }
315         catch ( LayoutException e )
316         {
317             /* eat it */
318         }
319         catch ( ProxyDownloadException e )
320         {
321             log.error(e.getMessage(), e);
322             throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to fetch artifact resource.");
323         }
324         return false;
325     }
326
327     private boolean fetchMetadataFromProxies(ManagedRepositoryContent managedRepository, DavServletRequest request, LogicalResource resource )
328         throws DavException
329     {
330         ProjectReference project;
331         VersionedReference versioned;
332
333         try
334         {
335
336             versioned = metadataTools.toVersionedReference( resource.getPath() );
337             if ( versioned != null )
338             {
339                 connectors.fetchFromProxies( managedRepository, versioned );
340                 return true;
341             }
342         }
343         catch ( RepositoryMetadataException e )
344         {
345             /* eat it */
346         }
347
348         try
349         {
350             project = metadataTools.toProjectReference( resource.getPath() );
351             if ( project != null )
352             {
353                 connectors.fetchFromProxies( managedRepository, project );
354                 return true;
355             }
356         }
357         catch ( RepositoryMetadataException e )
358         {
359             /* eat it */
360         }
361
362         return false;
363     }
364
365     /**
366      * A relocation capable client will request the POM prior to the artifact,
367      * and will then read meta-data and do client side relocation. A simplier
368      * client (like maven 1) will only request the artifact and not use the
369      * metadatas.
370      * <p>
371      * For such clients, archiva does server-side relocation by reading itself
372      * the &lt;relocation&gt; element in metadatas and serving the expected
373      * artifact.
374      */
375     protected void applyServerSideRelocation( ManagedRepositoryContent managedRepository, ArtifactReference artifact )
376         throws ProxyDownloadException
377     {
378         if ( "pom".equals( artifact.getType() ) )
379         {
380             return;
381         }
382
383         // Build the artifact POM reference
384         ArtifactReference pomReference = new ArtifactReference();
385         pomReference.setGroupId( artifact.getGroupId() );
386         pomReference.setArtifactId( artifact.getArtifactId() );
387         pomReference.setVersion( artifact.getVersion() );
388         pomReference.setType( "pom" );
389
390         // Get the artifact POM from proxied repositories if needed
391         connectors.fetchFromProxies( managedRepository, pomReference );
392
393         // Open and read the POM from the managed repo
394         File pom = managedRepository.toFile( pomReference );
395
396         if ( !pom.exists() )
397         {
398             return;
399         }
400
401         try
402         {
403             Model model = new MavenXpp3Reader().read( new FileReader( pom ) );
404             DistributionManagement dist = model.getDistributionManagement();
405             if ( dist != null )
406             {
407                 Relocation relocation = dist.getRelocation();
408                 if ( relocation != null )
409                 {
410                     // artifact is relocated : update the repositoryPath
411                     if ( relocation.getGroupId() != null )
412                     {
413                         artifact.setGroupId( relocation.getGroupId() );
414                     }
415                     if ( relocation.getArtifactId() != null )
416                     {
417                         artifact.setArtifactId( relocation.getArtifactId() );
418                     }
419                     if ( relocation.getVersion() != null )
420                     {
421                         artifact.setVersion( relocation.getVersion() );
422                     }
423                 }
424             }
425         }
426         catch ( FileNotFoundException e )
427         {
428             // Artifact has no POM in repo : ignore
429         }
430         catch ( IOException e )
431         {
432             // Unable to read POM : ignore.
433         }
434         catch ( XmlPullParserException e )
435         {
436             // Invalid POM : ignore
437         }
438     }
439
440     private void processAuditEvents( DavServletRequest request, String repositoryId, String resource,
441                                      boolean previouslyExisted, File resourceFile, String suffix )
442     {
443         if ( suffix == null )
444         {
445             suffix = "";
446         }
447
448         // Process Create Audit Events.
449         if ( !previouslyExisted && resourceFile.exists() )
450         {
451             if ( resourceFile.isFile() )
452             {
453                 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_FILE + suffix );
454             }
455             else if ( resourceFile.isDirectory() )
456             {
457                 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_DIR + suffix );
458             }
459         }
460         // Process Remove Audit Events.
461         else if ( previouslyExisted && !resourceFile.exists() )
462         {
463             if ( resourceFile.isFile() )
464             {
465                 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_FILE + suffix );
466             }
467             else if ( resourceFile.isDirectory() )
468             {
469                 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_DIR + suffix );
470             }
471         }
472         // Process modify events.
473         else
474         {
475             if ( resourceFile.isFile() )
476             {
477                 triggerAuditEvent( request, repositoryId, resource, AuditEvent.MODIFY_FILE + suffix );
478             }
479         }
480     }
481
482     private void triggerAuditEvent( String user, String remoteIP, String repositoryId, String resource, String action )
483     {
484         AuditEvent event = new AuditEvent( repositoryId, user, resource, action );
485         event.setRemoteIP( remoteIP );
486
487         for ( AuditListener listener : auditListeners )
488         {
489             listener.auditEvent( event );
490         }
491     }
492
493     private void triggerAuditEvent( DavServletRequest request, String repositoryId, String resource, String action )
494     {
495         triggerAuditEvent( ArchivaXworkUser.getActivePrincipal( ActionContext.getContext().getSession() ), getRemoteIP( request ), repositoryId, resource, action );
496     }
497
498     private String getRemoteIP( DavServletRequest request )
499     {
500         return request.getRemoteAddr();
501     }
502
503     public void addAuditListener( AuditListener listener )
504     {
505         this.auditListeners.add( listener );
506     }
507
508     public void clearAuditListeners()
509     {
510         this.auditListeners.clear();
511     }
512
513     public void removeAuditListener( AuditListener listener )
514     {
515         this.auditListeners.remove( listener );
516     }
517
518     private void setHeaders(DavResourceLocator locator, DavServletResponse response)
519     {
520         // [MRM-503] - Metadata file need Pragma:no-cache response
521         // header.
522         if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) )
523         {
524             response.addHeader( "Pragma", "no-cache" );
525             response.addHeader( "Cache-Control", "no-cache" );
526         }
527
528         // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots)
529     }
530
531     private ManagedRepositoryContent getManagedRepository(String respositoryId) throws DavException
532     {        
533         if (respositoryId != null)
534         {
535             try
536             {
537                 return repositoryFactory.getManagedRepositoryContent(respositoryId);
538             }
539             catch (RepositoryNotFoundException e)
540             {                
541                 throw new DavException(HttpServletResponse.SC_NOT_FOUND, e);
542             }
543             catch (RepositoryException e)
544             {                
545                 throw new DavException(HttpServletResponse.SC_NOT_FOUND, e);
546             }
547         }
548         return null;
549     }
550
551     private void checkLocatorIsInstanceOfRepositoryLocator(DavResourceLocator locator) throws DavException
552     {
553         if (!(locator instanceof RepositoryLocator))
554         {
555             throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Locator does not implement RepositoryLocator");
556         }
557     }
558
559     class LogicalResource
560     {
561         private String path;
562
563         public LogicalResource(String path)
564         {
565             this.path = path;
566         }
567
568         public String getPath()
569         {
570             return path;
571         }
572
573         public void setPath(String path)
574         {
575             this.path = path;
576         }
577     }
578 }