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