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.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;
55 import javax.servlet.http.HttpServletResponse;
56 import java.util.ArrayList;
57 import java.util.List;
61 * @author <a href="mailto:james@atlassian.com">James William Dumay</a>
62 * @plexus.component role="org.apache.maven.archiva.webdav.ArchivaDavResourceFactory"
64 public class ArchivaDavResourceFactory implements DavResourceFactory, Auditable
66 private Logger log = LoggerFactory.getLogger(ArchivaDavResourceFactory.class);
69 * @plexus.requirement role="org.apache.maven.archiva.repository.audit.AuditListener"
71 private List<AuditListener> auditListeners = new ArrayList<AuditListener>();
76 private RepositoryContentFactory repositoryFactory;
81 private RepositoryRequest repositoryRequest;
84 * @plexus.requirement role-hint="default"
86 private RepositoryProxyConnectors connectors;
91 private MetadataTools metadataTools;
96 private MimeTypes mimeTypes;
100 * @plexus.requirement
102 private ArchivaConfiguration archivaConfiguration;
104 public DavResource createResource(final DavResourceLocator locator, final DavServletRequest request, final DavServletResponse response) throws DavException
106 checkLocatorIsInstanceOfRepositoryLocator(locator);
107 ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator)locator;
109 RepositoryGroupConfiguration repoGroupConfig = archivaConfiguration.getConfiguration()
110 .getRepositoryGroupsAsMap().get( ( (RepositoryLocator) locator).getRepositoryId() );
112 List<String> repositories = new ArrayList<String>();
114 if ( repoGroupConfig != null )
116 if ( RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ).equals( "/" )
117 || WebdavMethodUtil.isWriteMethod( request.getMethod() ) )
119 throw new DavException( HttpServletResponse.SC_BAD_REQUEST, "Bad request to repository group <"
120 + repoGroupConfig.getId() + ">" );
122 repositories.addAll( repoGroupConfig.getRepositories() );
126 repositories.add( ( (RepositoryLocator) locator).getRepositoryId() );
129 DavResource resource = null;
130 DavException e = null;
132 for ( String repositoryId : repositories )
134 ManagedRepositoryContent managedRepository = null;
138 managedRepository = getManagedRepository( repositoryId );
140 catch ( DavException de )
142 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Invalid managed repository <" + repositoryId
146 if (!locator.getResourcePath().startsWith(ArchivaDavResource.HIDDEN_PATH_PREFIX))
148 if (managedRepository != null)
150 LogicalResource logicalResource = new LogicalResource(RepositoryPathUtil.getLogicalResource(locator.getResourcePath()));
152 boolean isGet = WebdavMethodUtil.isReadMethod( request.getMethod() );
153 boolean isPut = WebdavMethodUtil.isWriteMethod( request.getMethod() );
157 resource = doGet(managedRepository, request, archivaLocator, logicalResource);
162 resource = doPut(managedRepository, request, archivaLocator, logicalResource);
167 e = new DavException(HttpServletResponse.SC_NOT_FOUND, "Repository does not exist");
170 if (resource == null)
172 e = new DavException(HttpServletResponse.SC_NOT_FOUND, "Repository does not exist");
176 setHeaders(locator, response);
178 //compatibility with MRM-440 to ensure browsing the repository works ok
179 if (resource.isCollection() && !resource.getLocator().getResourcePath().endsWith("/"))
181 throw new BrowserRedirectException(resource.getHref());
192 public DavResource createResource(final DavResourceLocator locator, final DavSession davSession) throws DavException
194 checkLocatorIsInstanceOfRepositoryLocator(locator);
195 ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator)locator;
197 DavResource resource = null;
198 if (!locator.getResourcePath().startsWith(ArchivaDavResource.HIDDEN_PATH_PREFIX))
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);
208 private DavResource doGet(ManagedRepositoryContent managedRepository, DavServletRequest request, ArchivaDavResourceLocator locator, LogicalResource logicalResource) throws DavException
210 File resourceFile = new File ( managedRepository.getRepoRoot(), logicalResource.getPath());
211 ArchivaDavResource resource = new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator, this);
213 if ( !resource.isCollection() )
215 // At this point the incoming request can either be in default or
216 // legacy layout format.
217 boolean fromProxy = fetchContentFromProxies(managedRepository, request, logicalResource );
219 boolean previouslyExisted = resourceFile.exists();
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 );
228 catch ( LayoutException e )
230 if ( previouslyExisted )
234 throw new DavException(HttpServletResponse.SC_NOT_FOUND, e);
237 // Attempt to fetch the resource from any defined proxy.
240 processAuditEvents(request, locator.getWorkspaceName(), logicalResource.getPath(), previouslyExisted, resourceFile, " (proxied)");
242 resource = new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator, this);
244 if ( !resourceFile.exists() )
252 private DavResource doPut(ManagedRepositoryContent managedRepository, DavServletRequest request, ArchivaDavResourceLocator locator, LogicalResource logicalResource) throws DavException
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
263 File rootDirectory = new File(managedRepository.getRepoRoot());
264 File destDir = new File( rootDirectory, logicalResource.getPath() ).getParentFile();
265 if ( !destDir.exists() )
268 String relPath = PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
269 triggerAuditEvent(request, logicalResource.getPath(), relPath, AuditEvent.CREATE_DIR );
272 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
274 boolean previouslyExisted = resourceFile.exists();
276 processAuditEvents(request, locator.getRepositoryId(), logicalResource.getPath(), previouslyExisted, resourceFile, null );
278 return new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator, this);
281 private boolean fetchContentFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request, LogicalResource resource )
284 if ( repositoryRequest.isSupportFile( resource.getPath() ) )
286 // Checksums are fetched with artifact / metadata.
288 // Need to adjust the path for the checksum resource.
292 // Is it a Metadata resource?
293 if ( repositoryRequest.isDefault( resource.getPath() ) && repositoryRequest.isMetadata( resource.getPath() ) )
295 return fetchMetadataFromProxies(managedRepository, request, resource );
298 // Not any of the above? Then it's gotta be an artifact reference.
301 // Get the artifact reference in a layout neutral way.
302 ArtifactReference artifact = repositoryRequest.toArtifactReference( resource.getPath() );
304 if ( artifact != null )
306 applyServerSideRelocation(managedRepository, artifact );
308 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
310 resource.setPath( managedRepository.toPath( artifact ) );
312 return ( proxiedFile != null );
315 catch ( LayoutException e )
319 catch ( ProxyDownloadException e )
321 log.error(e.getMessage(), e);
322 throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to fetch artifact resource.");
327 private boolean fetchMetadataFromProxies(ManagedRepositoryContent managedRepository, DavServletRequest request, LogicalResource resource )
330 ProjectReference project;
331 VersionedReference versioned;
336 versioned = metadataTools.toVersionedReference( resource.getPath() );
337 if ( versioned != null )
339 connectors.fetchFromProxies( managedRepository, versioned );
343 catch ( RepositoryMetadataException e )
350 project = metadataTools.toProjectReference( resource.getPath() );
351 if ( project != null )
353 connectors.fetchFromProxies( managedRepository, project );
357 catch ( RepositoryMetadataException e )
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
371 * For such clients, archiva does server-side relocation by reading itself
372 * the <relocation> element in metadatas and serving the expected
375 protected void applyServerSideRelocation( ManagedRepositoryContent managedRepository, ArtifactReference artifact )
376 throws ProxyDownloadException
378 if ( "pom".equals( artifact.getType() ) )
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" );
390 // Get the artifact POM from proxied repositories if needed
391 connectors.fetchFromProxies( managedRepository, pomReference );
393 // Open and read the POM from the managed repo
394 File pom = managedRepository.toFile( pomReference );
403 Model model = new MavenXpp3Reader().read( new FileReader( pom ) );
404 DistributionManagement dist = model.getDistributionManagement();
407 Relocation relocation = dist.getRelocation();
408 if ( relocation != null )
410 // artifact is relocated : update the repositoryPath
411 if ( relocation.getGroupId() != null )
413 artifact.setGroupId( relocation.getGroupId() );
415 if ( relocation.getArtifactId() != null )
417 artifact.setArtifactId( relocation.getArtifactId() );
419 if ( relocation.getVersion() != null )
421 artifact.setVersion( relocation.getVersion() );
426 catch ( FileNotFoundException e )
428 // Artifact has no POM in repo : ignore
430 catch ( IOException e )
432 // Unable to read POM : ignore.
434 catch ( XmlPullParserException e )
436 // Invalid POM : ignore
440 private void processAuditEvents( DavServletRequest request, String repositoryId, String resource,
441 boolean previouslyExisted, File resourceFile, String suffix )
443 if ( suffix == null )
448 // Process Create Audit Events.
449 if ( !previouslyExisted && resourceFile.exists() )
451 if ( resourceFile.isFile() )
453 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_FILE + suffix );
455 else if ( resourceFile.isDirectory() )
457 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_DIR + suffix );
460 // Process Remove Audit Events.
461 else if ( previouslyExisted && !resourceFile.exists() )
463 if ( resourceFile.isFile() )
465 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_FILE + suffix );
467 else if ( resourceFile.isDirectory() )
469 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_DIR + suffix );
472 // Process modify events.
475 if ( resourceFile.isFile() )
477 triggerAuditEvent( request, repositoryId, resource, AuditEvent.MODIFY_FILE + suffix );
482 private void triggerAuditEvent( String user, String remoteIP, String repositoryId, String resource, String action )
484 AuditEvent event = new AuditEvent( repositoryId, user, resource, action );
485 event.setRemoteIP( remoteIP );
487 for ( AuditListener listener : auditListeners )
489 listener.auditEvent( event );
493 private void triggerAuditEvent( DavServletRequest request, String repositoryId, String resource, String action )
495 triggerAuditEvent( ArchivaXworkUser.getActivePrincipal( ActionContext.getContext().getSession() ), getRemoteIP( request ), repositoryId, resource, action );
498 private String getRemoteIP( DavServletRequest request )
500 return request.getRemoteAddr();
503 public void addAuditListener( AuditListener listener )
505 this.auditListeners.add( listener );
508 public void clearAuditListeners()
510 this.auditListeners.clear();
513 public void removeAuditListener( AuditListener listener )
515 this.auditListeners.remove( listener );
518 private void setHeaders(DavResourceLocator locator, DavServletResponse response)
520 // [MRM-503] - Metadata file need Pragma:no-cache response
522 if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) )
524 response.addHeader( "Pragma", "no-cache" );
525 response.addHeader( "Cache-Control", "no-cache" );
528 // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots)
531 private ManagedRepositoryContent getManagedRepository(String respositoryId) throws DavException
533 if (respositoryId != null)
537 return repositoryFactory.getManagedRepositoryContent(respositoryId);
539 catch (RepositoryNotFoundException e)
541 throw new DavException(HttpServletResponse.SC_NOT_FOUND, e);
543 catch (RepositoryException e)
545 throw new DavException(HttpServletResponse.SC_NOT_FOUND, e);
551 private void checkLocatorIsInstanceOfRepositoryLocator(DavResourceLocator locator) throws DavException
553 if (!(locator instanceof RepositoryLocator))
555 throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Locator does not implement RepositoryLocator");
559 class LogicalResource
563 public LogicalResource(String path)
568 public String getPath()
573 public void setPath(String path)