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