1 package org.apache.maven.archiva.web.repository;
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 org.apache.maven.archiva.common.utils.PathUtil;
23 import org.apache.maven.archiva.model.ArtifactReference;
24 import org.apache.maven.archiva.model.ProjectReference;
25 import org.apache.maven.archiva.model.VersionedReference;
26 import org.apache.maven.archiva.proxy.ProxyException;
27 import org.apache.maven.archiva.proxy.RepositoryProxyConnectors;
28 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
29 import org.apache.maven.archiva.repository.RepositoryContentFactory;
30 import org.apache.maven.archiva.repository.RepositoryException;
31 import org.apache.maven.archiva.repository.RepositoryNotFoundException;
32 import org.apache.maven.archiva.repository.audit.AuditEvent;
33 import org.apache.maven.archiva.repository.audit.AuditListener;
34 import org.apache.maven.archiva.repository.audit.Auditable;
35 import org.apache.maven.archiva.repository.content.RepositoryRequest;
36 import org.apache.maven.archiva.repository.layout.LayoutException;
37 import org.apache.maven.archiva.repository.metadata.MetadataTools;
38 import org.apache.maven.archiva.repository.metadata.RepositoryMetadataException;
39 import org.apache.maven.archiva.security.ArchivaUser;
40 import org.apache.maven.model.DistributionManagement;
41 import org.apache.maven.model.Model;
42 import org.apache.maven.model.Relocation;
43 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
44 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
45 import org.codehaus.plexus.webdav.AbstractDavServerComponent;
46 import org.codehaus.plexus.webdav.DavServerComponent;
47 import org.codehaus.plexus.webdav.DavServerException;
48 import org.codehaus.plexus.webdav.DavServerListener;
49 import org.codehaus.plexus.webdav.servlet.DavServerRequest;
50 import org.codehaus.plexus.webdav.util.WebdavMethodUtil;
53 import java.io.FileNotFoundException;
54 import java.io.FileReader;
55 import java.io.IOException;
56 import java.io.PrintWriter;
57 import java.util.ArrayList;
58 import java.util.List;
60 import javax.servlet.ServletConfig;
61 import javax.servlet.ServletException;
62 import javax.servlet.http.HttpServletResponse;
67 * @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
69 * @plexus.component role="org.codehaus.plexus.webdav.DavServerComponent"
70 * role-hint="proxied" instantiation-strategy="per-lookup"
72 public class ProxiedDavServer
73 extends AbstractDavServerComponent
77 * @plexus.requirement role-hint="simple"
79 private DavServerComponent davServer;
82 * @plexus.requirement role="org.apache.maven.archiva.repository.audit.AuditListener"
84 private List<AuditListener> auditListeners = new ArrayList<AuditListener>();
89 private RepositoryContentFactory repositoryFactory;
94 private RepositoryRequest repositoryRequest;
97 * @plexus.requirement role-hint="default"
99 private RepositoryProxyConnectors connectors;
102 * @plexus.requirement
104 private MetadataTools metadataTools;
107 * @plexus.requirement role-hint="xwork"
109 private ArchivaUser archivaUser;
111 private ManagedRepositoryContent managedRepository;
113 public String getPrefix()
115 return davServer.getPrefix();
118 public File getRootDirectory()
120 return davServer.getRootDirectory();
123 public void setPrefix( String prefix )
125 davServer.setPrefix( prefix );
128 public void setRootDirectory( File rootDirectory )
130 davServer.setRootDirectory( rootDirectory );
133 public void init( ServletConfig servletConfig )
134 throws DavServerException
136 davServer.init( servletConfig );
140 managedRepository = repositoryFactory.getManagedRepositoryContent( getPrefix() );
142 catch ( RepositoryNotFoundException e )
144 throw new DavServerException( e.getMessage(), e );
146 catch ( RepositoryException e )
148 throw new DavServerException( e.getMessage(), e );
152 public void process( DavServerRequest request, HttpServletResponse response )
153 throws DavServerException, ServletException, IOException
155 boolean isGet = WebdavMethodUtil.isReadMethod( request.getRequest().getMethod() );
156 boolean isPut = WebdavMethodUtil.isWriteMethod( request.getRequest().getMethod() );
157 String resource = request.getLogicalResource();
161 // Default behaviour is to treat the resource natively.
162 File resourceFile = new File( managedRepository.getRepoRoot(), resource );
164 // If this a directory resource, then we are likely browsing.
165 if ( resourceFile.exists() && resourceFile.isDirectory() )
167 String requestURL = request.getRequest().getRequestURL().toString();
169 // [MRM-440] - If webdav URL lacks a trailing /, navigating to all links in the listing return 404.
170 if( !requestURL.endsWith( "/" ) )
172 String redirectToLocation = requestURL + "/";
173 response.sendRedirect( redirectToLocation );
177 // Process the request.
178 davServer.process( request, response );
184 // At this point the incoming request can either be in default or legacy layout format.
187 // Perform an adjustment of the resource to the managed repository expected path.
188 resource = repositoryRequest.toNativePath( request.getLogicalResource(), managedRepository );
189 resourceFile = new File( managedRepository.getRepoRoot(), resource );
191 // Adjust the pathInfo resource to be in the format that the dav server impl expects.
192 request.getRequest().setPathInfo( resource );
194 boolean previouslyExisted = resourceFile.exists();
196 // Attempt to fetch the resource from any defined proxy.
197 if( fetchContentFromProxies( request, resource ) )
199 processAuditEvents( request, resource, previouslyExisted, resourceFile, " (proxied)" );
202 catch ( LayoutException e )
204 // Invalid resource, pass it on.
205 respondResourceMissing( request, response, e );
211 if ( resourceFile.exists() )
213 // [MRM-503] - Metadata file need Pragma:no-cache response header.
214 if ( request.getLogicalResource().endsWith( "/maven-metadata.xml" ) )
216 response.addHeader( "Pragma", "no-cache" );
217 response.addHeader( "Cache-Control", "no-cache" );
220 // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots)
222 davServer.process( request, response );
226 respondResourceMissing( request, response, null );
232 /* Create parent directories that don't exist when writing a file
233 * This actually makes this implementation not compliant to the
234 * WebDAV RFC - but we have enough knowledge
235 * about how the collection is being used to do this reasonably and
236 * some versions of Maven's WebDAV don't
237 * correctly create the collections themselves.
240 File rootDirectory = getRootDirectory();
241 if ( rootDirectory != null )
243 File destDir = new File( rootDirectory, resource ).getParentFile();
244 if( !destDir.exists() )
247 String relPath = PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
248 triggerAuditEvent( request, relPath, AuditEvent.CREATE_DIR );
252 File resourceFile = new File( managedRepository.getRepoRoot(), resource );
254 boolean previouslyExisted = resourceFile.exists();
256 // Allow the dav server to process the put request.
257 davServer.process( request, response );
259 processAuditEvents( request, resource, previouslyExisted, resourceFile, null );
266 private void respondResourceMissing( DavServerRequest request, HttpServletResponse response, Throwable t )
268 response.setStatus( HttpServletResponse.SC_NOT_FOUND );
272 StringBuffer missingUrl = new StringBuffer();
273 missingUrl.append( request.getRequest().getScheme() ).append( "://" );
274 missingUrl.append( request.getRequest().getServerName() ).append( ":" );
275 missingUrl.append( request.getRequest().getServerPort() );
276 missingUrl.append( request.getRequest().getServletPath() );
278 String message = "Error 404 Not Found";
280 PrintWriter out = new PrintWriter( response.getOutputStream() );
282 response.setContentType( "text/html; charset=\"UTF-8\"" );
284 out.println( "<html>" );
285 out.println( "<head><title>" + message + "</title></head>" );
286 out.println( "<body>" );
288 out.print( "<p><h1>" );
289 out.print( message );
290 out.println( "</h1></p>" );
292 out.print( "<p>The following resource does not exist: <a href=\"" );
293 out.print( missingUrl.toString() );
294 out.println( "\">" );
295 out.print( missingUrl.toString() );
296 out.println( "</a></p>" );
300 out.println( "<pre>" );
301 t.printStackTrace( out );
302 out.println( "</pre>" );
305 out.println( "</body></html>" );
309 catch ( IOException e )
315 private boolean fetchContentFromProxies( DavServerRequest request, String resource )
316 throws ServletException
318 if ( repositoryRequest.isSupportFile( resource ) )
320 // Checksums are fetched with artifact / metadata.
322 // Need to adjust the path for the checksum resource.
326 // Is it a Metadata resource?
327 if ( repositoryRequest.isDefault( resource ) && repositoryRequest.isMetadata( resource ) )
329 return fetchMetadataFromProxies( request, resource );
332 // Not any of the above? Then it's gotta be an artifact reference.
335 // Get the artifact reference in a layout neutral way.
336 ArtifactReference artifact = repositoryRequest.toArtifactReference( resource );
338 if ( artifact != null )
340 applyServerSideRelocation( artifact );
342 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
344 // Set the path to the resource using managed repository specific layout format.
345 request.getRequest().setPathInfo( managedRepository.toPath( artifact ) );
346 return ( proxiedFile != null );
349 catch ( LayoutException e )
353 catch ( ProxyException e )
355 throw new ServletException( "Unable to fetch artifact resource.", e );
360 private boolean fetchMetadataFromProxies( DavServerRequest request, String resource )
361 throws ServletException
363 ProjectReference project;
364 VersionedReference versioned;
369 versioned = metadataTools.toVersionedReference( resource );
370 if ( versioned != null )
372 connectors.fetchFromProxies( managedRepository, versioned );
376 catch ( RepositoryMetadataException e )
380 catch ( ProxyException e )
382 throw new ServletException( "Unable to fetch versioned metadata resource.", e );
387 project = metadataTools.toProjectReference( resource );
388 if ( project != null )
390 connectors.fetchFromProxies( managedRepository, project );
394 catch ( RepositoryMetadataException e )
398 catch ( ProxyException e )
400 throw new ServletException( "Unable to fetch project metadata resource.", e );
407 * A relocation capable client will request the POM prior to the artifact,
408 * and will then read meta-data and do client side relocation. A simplier
409 * client (like maven 1) will only request the artifact and not use the
412 * For such clients, archiva does server-side relocation by reading itself
413 * the <relocation> element in metadatas and serving the expected
416 protected void applyServerSideRelocation( ArtifactReference artifact )
417 throws ProxyException
419 if ( "pom".equals( artifact.getType() ) )
424 // Build the artifact POM reference
425 ArtifactReference pomReference = new ArtifactReference();
426 pomReference.setGroupId( artifact.getGroupId() );
427 pomReference.setArtifactId( artifact.getArtifactId() );
428 pomReference.setVersion( artifact.getVersion() );
429 pomReference.setType( "pom" );
431 // Get the artifact POM from proxied repositories if needed
432 connectors.fetchFromProxies( managedRepository, pomReference );
434 // Open and read the POM from the managed repo
435 File pom = managedRepository.toFile( pomReference );
444 Model model = new MavenXpp3Reader().read( new FileReader( pom ) );
445 DistributionManagement dist = model.getDistributionManagement();
448 Relocation relocation = dist.getRelocation();
449 if ( relocation != null )
451 // artifact is relocated : update the repositoryPath
452 if ( relocation.getGroupId() != null )
454 artifact.setGroupId( relocation.getGroupId() );
456 if ( relocation.getArtifactId() != null )
458 artifact.setArtifactId( relocation.getArtifactId() );
460 if ( relocation.getVersion() != null )
462 artifact.setVersion( relocation.getVersion() );
467 catch ( FileNotFoundException e )
469 // Artifact has no POM in repo : ignore
471 catch ( IOException e )
473 // Unable to read POM : ignore.
475 catch ( XmlPullParserException e )
477 // Invalid POM : ignore
482 public void addListener( DavServerListener listener )
484 super.addListener( listener );
485 davServer.addListener( listener );
489 public boolean isUseIndexHtml()
491 return davServer.isUseIndexHtml();
495 public boolean hasResource( String resource )
497 return davServer.hasResource( resource );
501 public void removeListener( DavServerListener listener )
503 davServer.removeListener( listener );
507 public void setUseIndexHtml( boolean useIndexHtml )
509 super.setUseIndexHtml( useIndexHtml );
510 davServer.setUseIndexHtml( useIndexHtml );
513 public ManagedRepositoryContent getRepository()
515 return managedRepository;
518 private void processAuditEvents( DavServerRequest request, String resource, boolean previouslyExisted,
519 File resourceFile, String suffix )
526 // Process Create Audit Events.
527 if ( !previouslyExisted && resourceFile.exists() )
529 if ( resourceFile.isFile() )
531 triggerAuditEvent( request, resource, AuditEvent.CREATE_FILE + suffix );
533 else if ( resourceFile.isDirectory() )
535 triggerAuditEvent( request, resource, AuditEvent.CREATE_DIR + suffix );
538 // Process Remove Audit Events.
539 else if ( previouslyExisted && !resourceFile.exists() )
541 if ( resourceFile.isFile() )
543 triggerAuditEvent( request, resource, AuditEvent.REMOVE_FILE + suffix );
545 else if ( resourceFile.isDirectory() )
547 triggerAuditEvent( request, resource, AuditEvent.REMOVE_DIR + suffix );
550 // Process modify events.
553 if ( resourceFile.isFile() )
555 triggerAuditEvent( request, resource, AuditEvent.MODIFY_FILE + suffix );
560 private void triggerAuditEvent( String user, String remoteIP, String resource, String action )
562 AuditEvent event = new AuditEvent( this.getPrefix(), user, resource, action );
563 event.setRemoteIP( remoteIP );
565 for ( AuditListener listener : auditListeners )
567 listener.auditEvent( event );
571 private void triggerAuditEvent( DavServerRequest request, String resource, String action )
573 triggerAuditEvent( archivaUser.getActivePrincipal(), getRemoteIP( request ), resource, action );
576 private String getRemoteIP( DavServerRequest request )
578 return request.getRequest().getRemoteAddr();
581 public void addAuditListener( AuditListener listener )
583 this.auditListeners.add( listener );
586 public void clearAuditListeners()
588 this.auditListeners.clear();
591 public void removeAuditListener( AuditListener listener )
593 this.auditListeners.remove( listener );