1 package org.apache.maven.archiva.webdav;
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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
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;
53 import javax.servlet.http.HttpServletResponse;
54 import javax.servlet.ServletException;
55 import java.util.ArrayList;
56 import java.util.List;
60 * @author <a href="mailto:james@atlassian.com">James William Dumay</a>
61 * @plexus.component role="org.apache.maven.archiva.webdav.ArchivaDavResourceFactory"
63 public class ArchivaDavResourceFactory implements DavResourceFactory, Auditable
65 private Logger log = LoggerFactory.getLogger(ArchivaDavResourceFactory.class);
68 * @plexus.requirement role="org.apache.maven.archiva.repository.audit.AuditListener"
70 private List<AuditListener> auditListeners = new ArrayList<AuditListener>();
75 private RepositoryContentFactory repositoryFactory;
80 private RepositoryRequest repositoryRequest;
83 * @plexus.requirement role-hint="default"
85 private RepositoryProxyConnectors connectors;
90 private MetadataTools metadataTools;
95 private MimeTypes mimeTypes;
97 public DavResource createResource(final DavResourceLocator locator, final DavServletRequest request, final DavServletResponse response) throws DavException
99 final ManagedRepositoryContent managedRepository = getManagedRepository(locator.getWorkspaceName());
100 final LogicalResource logicalResource = new LogicalResource(RepositoryPathUtil.getLogicalResource(locator.getResourcePath()));
102 DavResource resource = null;
104 if (managedRepository != null)
106 final boolean isGet = WebdavMethodUtil.isReadMethod( request.getMethod() );
107 final boolean isPut = WebdavMethodUtil.isWriteMethod( request.getMethod() );
111 resource = doGet(managedRepository, request, locator, logicalResource);
116 resource = doPut(managedRepository, request, locator, logicalResource);
121 throw new DavException(HttpServletResponse.SC_NOT_FOUND, "Repository does not exist");
124 if (resource != null)
126 setHeaders(locator, response);
130 throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not get resource for method " + request.getMethod());
133 public DavResource createResource(final DavResourceLocator locator, final DavSession davSession) throws DavException
135 final ManagedRepositoryContent managedRepository = getManagedRepository(locator.getWorkspaceName());
136 final String logicalResource = RepositoryPathUtil.getLogicalResource(locator.getResourcePath());
137 final File resourceFile = new File ( managedRepository.getRepoRoot(), logicalResource);
139 return new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource, mimeTypes, locator, this, null);
142 private DavResource doGet(ManagedRepositoryContent managedRepository, DavServletRequest request, DavResourceLocator locator, LogicalResource logicalResource) throws DavException
144 File resourceFile = new File ( managedRepository.getRepoRoot(), logicalResource.getPath());
145 ArchivaDavResource resource = new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator, this, null);
147 if ( !resource.isCollection() )
149 // At this point the incoming request can either be in default or
150 // legacy layout format.
153 boolean fromProxy = fetchContentFromProxies(managedRepository, request, logicalResource );
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 );
160 boolean previouslyExisted = resourceFile.exists();
162 // Attempt to fetch the resource from any defined proxy.
165 processAuditEvents(request, locator.getWorkspaceName(), logicalResource.getPath(), previouslyExisted, resourceFile, " (proxied)");
167 resource = new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator, this, null);
170 catch ( LayoutException e )
172 throw new DavException(HttpServletResponse.SC_NOT_FOUND, e);
178 private DavResource doPut(ManagedRepositoryContent managedRepository, DavServletRequest request, DavResourceLocator locator, LogicalResource logicalResource) throws DavException
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
189 File rootDirectory = new File(managedRepository.getRepoRoot());
190 File destDir = new File( rootDirectory, logicalResource.getPath() ).getParentFile();
191 if ( !destDir.exists() )
195 PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
196 triggerAuditEvent(request, logicalResource.getPath(), relPath, AuditEvent.CREATE_DIR );
199 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
201 boolean previouslyExisted = resourceFile.exists();
203 processAuditEvents(request, locator.getWorkspaceName(), logicalResource.getPath(), previouslyExisted, resourceFile, null );
205 return new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator, this, null);
208 private boolean fetchContentFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request, LogicalResource resource )
211 if ( repositoryRequest.isSupportFile( resource.getPath() ) )
213 // Checksums are fetched with artifact / metadata.
215 // Need to adjust the path for the checksum resource.
219 // Is it a Metadata resource?
220 if ( repositoryRequest.isDefault( resource.getPath() ) && repositoryRequest.isMetadata( resource.getPath() ) )
222 return fetchMetadataFromProxies(managedRepository, request, resource );
225 // Not any of the above? Then it's gotta be an artifact reference.
228 // Get the artifact reference in a layout neutral way.
229 ArtifactReference artifact = repositoryRequest.toArtifactReference( resource.getPath() );
231 if ( artifact != null )
233 applyServerSideRelocation(managedRepository, artifact );
235 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
237 resource.setPath( managedRepository.toPath( artifact ) );
239 return ( proxiedFile != null );
242 catch ( LayoutException e )
246 catch ( ProxyDownloadException e )
248 log.error(e.getMessage(), e);
249 throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to fetch artifact resource.");
254 private boolean fetchMetadataFromProxies(ManagedRepositoryContent managedRepository, DavServletRequest request, LogicalResource resource )
257 ProjectReference project;
258 VersionedReference versioned;
263 versioned = metadataTools.toVersionedReference( resource.getPath() );
264 if ( versioned != null )
266 connectors.fetchFromProxies( managedRepository, versioned );
270 catch ( RepositoryMetadataException e )
272 log.error(e.getMessage(), e);
277 project = metadataTools.toProjectReference( resource.getPath() );
278 if ( project != null )
280 connectors.fetchFromProxies( managedRepository, project );
284 catch ( RepositoryMetadataException e )
286 log.error(e.getMessage(), e);
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
298 * For such clients, archiva does server-side relocation by reading itself
299 * the <relocation> element in metadatas and serving the expected
302 protected void applyServerSideRelocation( ManagedRepositoryContent managedRepository, ArtifactReference artifact )
303 throws ProxyDownloadException
305 if ( "pom".equals( artifact.getType() ) )
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" );
317 // Get the artifact POM from proxied repositories if needed
318 connectors.fetchFromProxies( managedRepository, pomReference );
320 // Open and read the POM from the managed repo
321 File pom = managedRepository.toFile( pomReference );
330 Model model = new MavenXpp3Reader().read( new FileReader( pom ) );
331 DistributionManagement dist = model.getDistributionManagement();
334 Relocation relocation = dist.getRelocation();
335 if ( relocation != null )
337 // artifact is relocated : update the repositoryPath
338 if ( relocation.getGroupId() != null )
340 artifact.setGroupId( relocation.getGroupId() );
342 if ( relocation.getArtifactId() != null )
344 artifact.setArtifactId( relocation.getArtifactId() );
346 if ( relocation.getVersion() != null )
348 artifact.setVersion( relocation.getVersion() );
353 catch ( FileNotFoundException e )
355 // Artifact has no POM in repo : ignore
357 catch ( IOException e )
359 // Unable to read POM : ignore.
361 catch ( XmlPullParserException e )
363 // Invalid POM : ignore
367 private void processAuditEvents( DavServletRequest request, String repositoryId, String resource,
368 boolean previouslyExisted, File resourceFile, String suffix )
370 if ( suffix == null )
375 // Process Create Audit Events.
376 if ( !previouslyExisted && resourceFile.exists() )
378 if ( resourceFile.isFile() )
380 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_FILE + suffix );
382 else if ( resourceFile.isDirectory() )
384 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_DIR + suffix );
387 // Process Remove Audit Events.
388 else if ( previouslyExisted && !resourceFile.exists() )
390 if ( resourceFile.isFile() )
392 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_FILE + suffix );
394 else if ( resourceFile.isDirectory() )
396 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_DIR + suffix );
399 // Process modify events.
402 if ( resourceFile.isFile() )
404 triggerAuditEvent( request, repositoryId, resource, AuditEvent.MODIFY_FILE + suffix );
409 private void triggerAuditEvent( String user, String remoteIP, String repositoryId, String resource, String action )
411 AuditEvent event = new AuditEvent( repositoryId, user, resource, action );
412 event.setRemoteIP( remoteIP );
414 for ( AuditListener listener : auditListeners )
416 listener.auditEvent( event );
420 private void triggerAuditEvent( DavServletRequest request, String repositoryId, String resource, String action )
422 triggerAuditEvent( ArchivaXworkUser.getActivePrincipal( ActionContext.getContext().getSession() ), getRemoteIP( request ), repositoryId, resource, action );
425 private String getRemoteIP( DavServletRequest request )
427 return request.getRemoteAddr();
430 public void addAuditListener( AuditListener listener )
432 this.auditListeners.add( listener );
435 public void clearAuditListeners()
437 this.auditListeners.clear();
440 public void removeAuditListener( AuditListener listener )
442 this.auditListeners.remove( listener );
445 private void setHeaders(DavResourceLocator locator, DavServletResponse response)
447 // [MRM-503] - Metadata file need Pragma:no-cache response
449 if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) )
451 response.addHeader( "Pragma", "no-cache" );
452 response.addHeader( "Cache-Control", "no-cache" );
455 // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots)
458 private ManagedRepositoryContent getManagedRepository(String respositoryId) throws DavException
460 if (respositoryId != null)
464 return repositoryFactory.getManagedRepositoryContent(respositoryId);
466 catch (RepositoryNotFoundException e)
468 throw new DavException(HttpServletResponse.SC_NOT_FOUND, e);
470 catch (RepositoryException e)
472 throw new DavException(HttpServletResponse.SC_NOT_FOUND, e);
478 class LogicalResource
482 public LogicalResource(String path)
487 public String getPath()
492 public void setPath(String path)