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 java.util.ArrayList;
55 import java.util.List;
59 * @author <a href="mailto:james@atlassian.com">James William Dumay</a>
60 * @plexus.component role="org.apache.maven.archiva.webdav.ArchivaDavResourceFactory"
62 public class ArchivaDavResourceFactory implements DavResourceFactory, Auditable
64 private Logger log = LoggerFactory.getLogger(ArchivaDavResourceFactory.class);
67 * @plexus.requirement role="org.apache.maven.archiva.repository.audit.AuditListener"
69 private List<AuditListener> auditListeners = new ArrayList<AuditListener>();
74 private RepositoryContentFactory repositoryFactory;
79 private RepositoryRequest repositoryRequest;
82 * @plexus.requirement role-hint="default"
84 private RepositoryProxyConnectors connectors;
89 private MetadataTools metadataTools;
94 private MimeTypes mimeTypes;
96 public DavResource createResource(final DavResourceLocator locator, final DavServletRequest request, final DavServletResponse response) throws DavException
98 checkLocatorIsInstanceOfRepositoryLocator(locator);
99 ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator)locator;
101 DavResource resource = null;
103 if (!locator.getResourcePath().startsWith(ArchivaDavResource.HIDDEN_PATH_PREFIX))
105 final ManagedRepositoryContent managedRepository = getManagedRepository(((RepositoryLocator)locator).getRepositoryId());
107 if (managedRepository != null)
109 LogicalResource logicalResource = new LogicalResource(RepositoryPathUtil.getLogicalResource(locator.getResourcePath()));
110 boolean isGet = WebdavMethodUtil.isReadMethod( request.getMethod() );
111 boolean isPut = WebdavMethodUtil.isWriteMethod( request.getMethod() );
115 resource = doGet(managedRepository, request, archivaLocator, logicalResource);
120 resource = doPut(managedRepository, request, archivaLocator, logicalResource);
125 throw new DavException(HttpServletResponse.SC_NOT_FOUND, "Repository does not exist");
128 if (resource == null)
130 throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not get resource for method " + request.getMethod());
132 setHeaders(locator, response);
137 public DavResource createResource(final DavResourceLocator locator, final DavSession davSession) throws DavException
139 checkLocatorIsInstanceOfRepositoryLocator(locator);
140 ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator)locator;
142 DavResource resource = null;
143 if (!locator.getResourcePath().startsWith(ArchivaDavResource.HIDDEN_PATH_PREFIX))
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);
153 private DavResource doGet(ManagedRepositoryContent managedRepository, DavServletRequest request, ArchivaDavResourceLocator locator, LogicalResource logicalResource) throws DavException
155 File resourceFile = new File ( managedRepository.getRepoRoot(), logicalResource.getPath());
156 ArchivaDavResource resource = new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator, this);
158 if ( !resource.isCollection() )
160 // At this point the incoming request can either be in default or
161 // legacy layout format.
162 boolean fromProxy = fetchContentFromProxies(managedRepository, request, logicalResource );
164 boolean previouslyExisted = resourceFile.exists();
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 );
173 catch ( LayoutException e )
175 if ( previouslyExisted )
179 throw new DavException(HttpServletResponse.SC_NOT_FOUND, e);
182 // Attempt to fetch the resource from any defined proxy.
185 processAuditEvents(request, locator.getWorkspaceName(), logicalResource.getPath(), previouslyExisted, resourceFile, " (proxied)");
187 resource = new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator, this);
192 private DavResource doPut(ManagedRepositoryContent managedRepository, DavServletRequest request, ArchivaDavResourceLocator locator, LogicalResource logicalResource) throws DavException
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
203 File rootDirectory = new File(managedRepository.getRepoRoot());
204 File destDir = new File( rootDirectory, logicalResource.getPath() ).getParentFile();
205 if ( !destDir.exists() )
208 String relPath = PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
209 triggerAuditEvent(request, logicalResource.getPath(), relPath, AuditEvent.CREATE_DIR );
212 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
214 boolean previouslyExisted = resourceFile.exists();
216 processAuditEvents(request, locator.getRepositoryId(), logicalResource.getPath(), previouslyExisted, resourceFile, null );
218 return new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator, this);
221 private boolean fetchContentFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request, LogicalResource resource )
224 if ( repositoryRequest.isSupportFile( resource.getPath() ) )
226 // Checksums are fetched with artifact / metadata.
228 // Need to adjust the path for the checksum resource.
232 // Is it a Metadata resource?
233 if ( repositoryRequest.isDefault( resource.getPath() ) && repositoryRequest.isMetadata( resource.getPath() ) )
235 return fetchMetadataFromProxies(managedRepository, request, resource );
238 // Not any of the above? Then it's gotta be an artifact reference.
241 // Get the artifact reference in a layout neutral way.
242 ArtifactReference artifact = repositoryRequest.toArtifactReference( resource.getPath() );
244 if ( artifact != null )
246 applyServerSideRelocation(managedRepository, artifact );
248 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
250 resource.setPath( managedRepository.toPath( artifact ) );
252 return ( proxiedFile != null );
255 catch ( LayoutException e )
259 catch ( ProxyDownloadException e )
261 log.error(e.getMessage(), e);
262 throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to fetch artifact resource.");
267 private boolean fetchMetadataFromProxies(ManagedRepositoryContent managedRepository, DavServletRequest request, LogicalResource resource )
270 ProjectReference project;
271 VersionedReference versioned;
276 versioned = metadataTools.toVersionedReference( resource.getPath() );
277 if ( versioned != null )
279 connectors.fetchFromProxies( managedRepository, versioned );
283 catch ( RepositoryMetadataException e )
290 project = metadataTools.toProjectReference( resource.getPath() );
291 if ( project != null )
293 connectors.fetchFromProxies( managedRepository, project );
297 catch ( RepositoryMetadataException e )
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
311 * For such clients, archiva does server-side relocation by reading itself
312 * the <relocation> element in metadatas and serving the expected
315 protected void applyServerSideRelocation( ManagedRepositoryContent managedRepository, ArtifactReference artifact )
316 throws ProxyDownloadException
318 if ( "pom".equals( artifact.getType() ) )
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" );
330 // Get the artifact POM from proxied repositories if needed
331 connectors.fetchFromProxies( managedRepository, pomReference );
333 // Open and read the POM from the managed repo
334 File pom = managedRepository.toFile( pomReference );
343 Model model = new MavenXpp3Reader().read( new FileReader( pom ) );
344 DistributionManagement dist = model.getDistributionManagement();
347 Relocation relocation = dist.getRelocation();
348 if ( relocation != null )
350 // artifact is relocated : update the repositoryPath
351 if ( relocation.getGroupId() != null )
353 artifact.setGroupId( relocation.getGroupId() );
355 if ( relocation.getArtifactId() != null )
357 artifact.setArtifactId( relocation.getArtifactId() );
359 if ( relocation.getVersion() != null )
361 artifact.setVersion( relocation.getVersion() );
366 catch ( FileNotFoundException e )
368 // Artifact has no POM in repo : ignore
370 catch ( IOException e )
372 // Unable to read POM : ignore.
374 catch ( XmlPullParserException e )
376 // Invalid POM : ignore
380 private void processAuditEvents( DavServletRequest request, String repositoryId, String resource,
381 boolean previouslyExisted, File resourceFile, String suffix )
383 if ( suffix == null )
388 // Process Create Audit Events.
389 if ( !previouslyExisted && resourceFile.exists() )
391 if ( resourceFile.isFile() )
393 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_FILE + suffix );
395 else if ( resourceFile.isDirectory() )
397 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_DIR + suffix );
400 // Process Remove Audit Events.
401 else if ( previouslyExisted && !resourceFile.exists() )
403 if ( resourceFile.isFile() )
405 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_FILE + suffix );
407 else if ( resourceFile.isDirectory() )
409 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_DIR + suffix );
412 // Process modify events.
415 if ( resourceFile.isFile() )
417 triggerAuditEvent( request, repositoryId, resource, AuditEvent.MODIFY_FILE + suffix );
422 private void triggerAuditEvent( String user, String remoteIP, String repositoryId, String resource, String action )
424 AuditEvent event = new AuditEvent( repositoryId, user, resource, action );
425 event.setRemoteIP( remoteIP );
427 for ( AuditListener listener : auditListeners )
429 listener.auditEvent( event );
433 private void triggerAuditEvent( DavServletRequest request, String repositoryId, String resource, String action )
435 triggerAuditEvent( ArchivaXworkUser.getActivePrincipal( ActionContext.getContext().getSession() ), getRemoteIP( request ), repositoryId, resource, action );
438 private String getRemoteIP( DavServletRequest request )
440 return request.getRemoteAddr();
443 public void addAuditListener( AuditListener listener )
445 this.auditListeners.add( listener );
448 public void clearAuditListeners()
450 this.auditListeners.clear();
453 public void removeAuditListener( AuditListener listener )
455 this.auditListeners.remove( listener );
458 private void setHeaders(DavResourceLocator locator, DavServletResponse response)
460 // [MRM-503] - Metadata file need Pragma:no-cache response
462 if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) )
464 response.addHeader( "Pragma", "no-cache" );
465 response.addHeader( "Cache-Control", "no-cache" );
468 // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots)
471 private ManagedRepositoryContent getManagedRepository(String respositoryId) throws DavException
473 if (respositoryId != null)
477 return repositoryFactory.getManagedRepositoryContent(respositoryId);
479 catch (RepositoryNotFoundException e)
481 throw new DavException(HttpServletResponse.SC_NOT_FOUND, e);
483 catch (RepositoryException e)
485 throw new DavException(HttpServletResponse.SC_NOT_FOUND, e);
491 private void checkLocatorIsInstanceOfRepositoryLocator(DavResourceLocator locator) throws DavException
493 if (!(locator instanceof RepositoryLocator))
495 throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Locator does not implement RepositoryLocator");
499 class LogicalResource
503 public LogicalResource(String path)
508 public String getPath()
513 public void setPath(String path)