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
23 import java.io.FileNotFoundException;
24 import java.io.FileReader;
25 import java.io.IOException;
26 import java.io.PrintWriter;
27 import java.util.ArrayList;
28 import java.util.List;
30 import javax.servlet.ServletConfig;
31 import javax.servlet.ServletException;
32 import javax.servlet.http.HttpServletResponse;
34 import org.apache.maven.archiva.common.utils.PathUtil;
35 import org.apache.maven.archiva.model.ArtifactReference;
36 import org.apache.maven.archiva.model.ProjectReference;
37 import org.apache.maven.archiva.model.VersionedReference;
38 import org.apache.maven.archiva.proxy.ProxyException;
39 import org.apache.maven.archiva.proxy.RepositoryProxyConnectors;
40 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
41 import org.apache.maven.archiva.repository.RepositoryContentFactory;
42 import org.apache.maven.archiva.repository.RepositoryException;
43 import org.apache.maven.archiva.repository.RepositoryNotFoundException;
44 import org.apache.maven.archiva.repository.audit.AuditEvent;
45 import org.apache.maven.archiva.repository.audit.AuditListener;
46 import org.apache.maven.archiva.repository.audit.Auditable;
47 import org.apache.maven.archiva.repository.content.RepositoryRequest;
48 import org.apache.maven.archiva.repository.layout.LayoutException;
49 import org.apache.maven.archiva.repository.metadata.MetadataTools;
50 import org.apache.maven.archiva.repository.metadata.RepositoryMetadataException;
51 import org.apache.maven.archiva.security.ArchivaUser;
52 import org.apache.maven.model.DistributionManagement;
53 import org.apache.maven.model.Model;
54 import org.apache.maven.model.Relocation;
55 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
56 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
57 import org.codehaus.plexus.webdav.AbstractDavServerComponent;
58 import org.codehaus.plexus.webdav.DavServerComponent;
59 import org.codehaus.plexus.webdav.DavServerException;
60 import org.codehaus.plexus.webdav.DavServerListener;
61 import org.codehaus.plexus.webdav.servlet.DavServerRequest;
62 import org.codehaus.plexus.webdav.util.WebdavMethodUtil;
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
170 // all links in the listing return 404.
171 if ( !requestURL.endsWith( "/" ) )
173 String redirectToLocation = requestURL + "/";
174 response.sendRedirect( redirectToLocation );
178 // Process the request.
179 davServer.process( request, response );
185 // At this point the incoming request can either be in default or
186 // legacy layout format.
189 boolean fromProxy = fetchContentFromProxies( request, resource );
191 // Perform an adjustment of the resource to the managed
192 // repository expected path.
195 .toNativePath( request.getLogicalResource(), managedRepository );
196 resourceFile = new File( managedRepository.getRepoRoot(), resource );
198 // Adjust the pathInfo resource to be in the format that the dav
199 // server impl expects.
200 request.setLogicalResource( resource );
202 boolean previouslyExisted = resourceFile.exists();
204 // Attempt to fetch the resource from any defined proxy.
207 processAuditEvents( request, resource, previouslyExisted, resourceFile,
211 catch ( LayoutException e )
213 // Invalid resource, pass it on.
214 respondResourceMissing( request, response, e );
220 if ( resourceFile.exists() )
222 // [MRM-503] - Metadata file need Pragma:no-cache response
224 if ( request.getLogicalResource().endsWith( "/maven-metadata.xml" ) )
226 response.addHeader( "Pragma", "no-cache" );
227 response.addHeader( "Cache-Control", "no-cache" );
230 // TODO: [MRM-524] determine http caching options for other
231 // types of files (artifacts, sha1, md5, snapshots)
233 davServer.process( request, response );
237 respondResourceMissing( request, response, null );
244 * Create parent directories that don't exist when writing a file
245 * This actually makes this implementation not compliant to the
246 * WebDAV RFC - but we have enough knowledge about how the
247 * collection is being used to do this reasonably and some versions
248 * of Maven's WebDAV don't correctly create the collections
252 File rootDirectory = getRootDirectory();
253 if ( rootDirectory != null )
255 File destDir = new File( rootDirectory, resource ).getParentFile();
256 if ( !destDir.exists() )
260 PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
261 triggerAuditEvent( request, relPath, AuditEvent.CREATE_DIR );
265 File resourceFile = new File( managedRepository.getRepoRoot(), resource );
267 boolean previouslyExisted = resourceFile.exists();
269 // Allow the dav server to process the put request.
270 davServer.process( request, response );
272 processAuditEvents( request, resource, previouslyExisted, resourceFile, null );
279 private void respondResourceMissing( DavServerRequest request, HttpServletResponse response,
282 response.setStatus( HttpServletResponse.SC_NOT_FOUND );
286 StringBuffer missingUrl = new StringBuffer();
287 missingUrl.append( request.getRequest().getScheme() ).append( "://" );
288 missingUrl.append( request.getRequest().getServerName() ).append( ":" );
289 missingUrl.append( request.getRequest().getServerPort() );
290 missingUrl.append( request.getRequest().getServletPath() );
292 String message = "Error 404 Not Found";
294 PrintWriter out = new PrintWriter( response.getOutputStream() );
296 response.setContentType( "text/html; charset=\"UTF-8\"" );
298 out.println( "<html>" );
299 out.println( "<head><title>" + message + "</title></head>" );
300 out.println( "<body>" );
302 out.print( "<p><h1>" );
303 out.print( message );
304 out.println( "</h1></p>" );
306 out.print( "<p>The following resource does not exist: <a href=\"" );
307 out.print( missingUrl.toString() );
308 out.println( "\">" );
309 out.print( missingUrl.toString() );
310 out.println( "</a></p>" );
314 out.println( "<pre>" );
315 t.printStackTrace( out );
316 out.println( "</pre>" );
319 out.println( "</body></html>" );
323 catch ( IOException e )
329 private boolean fetchContentFromProxies( DavServerRequest request, String resource )
330 throws ServletException
332 if ( repositoryRequest.isSupportFile( resource ) )
334 // Checksums are fetched with artifact / metadata.
336 // Need to adjust the path for the checksum resource.
340 // Is it a Metadata resource?
341 if ( repositoryRequest.isDefault( resource ) && repositoryRequest.isMetadata( resource ) )
343 return fetchMetadataFromProxies( request, resource );
346 // Not any of the above? Then it's gotta be an artifact reference.
349 // Get the artifact reference in a layout neutral way.
350 ArtifactReference artifact = repositoryRequest.toArtifactReference( resource );
352 if ( artifact != null )
354 applyServerSideRelocation( artifact );
356 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
358 // Set the path to the resource using managed repository
359 // specific layout format.
360 request.setLogicalResource( managedRepository.toPath( artifact ) );
361 return ( proxiedFile != null );
364 catch ( LayoutException e )
368 catch ( ProxyException e )
370 throw new ServletException( "Unable to fetch artifact resource.", e );
375 private boolean fetchMetadataFromProxies( DavServerRequest request, String resource )
376 throws ServletException
378 ProjectReference project;
379 VersionedReference versioned;
384 versioned = metadataTools.toVersionedReference( resource );
385 if ( versioned != null )
387 connectors.fetchFromProxies( managedRepository, versioned );
391 catch ( RepositoryMetadataException e )
395 catch ( ProxyException e )
397 throw new ServletException( "Unable to fetch versioned metadata resource.", e );
402 project = metadataTools.toProjectReference( resource );
403 if ( project != null )
405 connectors.fetchFromProxies( managedRepository, project );
409 catch ( RepositoryMetadataException e )
413 catch ( ProxyException e )
415 throw new ServletException( "Unable to fetch project metadata resource.", e );
422 * A relocation capable client will request the POM prior to the artifact,
423 * and will then read meta-data and do client side relocation. A simplier
424 * client (like maven 1) will only request the artifact and not use the
427 * For such clients, archiva does server-side relocation by reading itself
428 * the <relocation> element in metadatas and serving the expected
431 protected void applyServerSideRelocation( ArtifactReference artifact )
432 throws ProxyException
434 if ( "pom".equals( artifact.getType() ) )
439 // Build the artifact POM reference
440 ArtifactReference pomReference = new ArtifactReference();
441 pomReference.setGroupId( artifact.getGroupId() );
442 pomReference.setArtifactId( artifact.getArtifactId() );
443 pomReference.setVersion( artifact.getVersion() );
444 pomReference.setType( "pom" );
446 // Get the artifact POM from proxied repositories if needed
447 connectors.fetchFromProxies( managedRepository, pomReference );
449 // Open and read the POM from the managed repo
450 File pom = managedRepository.toFile( pomReference );
459 Model model = new MavenXpp3Reader().read( new FileReader( pom ) );
460 DistributionManagement dist = model.getDistributionManagement();
463 Relocation relocation = dist.getRelocation();
464 if ( relocation != null )
466 // artifact is relocated : update the repositoryPath
467 if ( relocation.getGroupId() != null )
469 artifact.setGroupId( relocation.getGroupId() );
471 if ( relocation.getArtifactId() != null )
473 artifact.setArtifactId( relocation.getArtifactId() );
475 if ( relocation.getVersion() != null )
477 artifact.setVersion( relocation.getVersion() );
482 catch ( FileNotFoundException e )
484 // Artifact has no POM in repo : ignore
486 catch ( IOException e )
488 // Unable to read POM : ignore.
490 catch ( XmlPullParserException e )
492 // Invalid POM : ignore
497 public void addListener( DavServerListener listener )
499 super.addListener( listener );
500 davServer.addListener( listener );
504 public boolean isUseIndexHtml()
506 return davServer.isUseIndexHtml();
510 public boolean hasResource( String resource )
512 return davServer.hasResource( resource );
516 public void removeListener( DavServerListener listener )
518 davServer.removeListener( listener );
522 public void setUseIndexHtml( boolean useIndexHtml )
524 super.setUseIndexHtml( useIndexHtml );
525 davServer.setUseIndexHtml( useIndexHtml );
528 public ManagedRepositoryContent getRepository()
530 return managedRepository;
533 private void processAuditEvents( DavServerRequest request, String resource,
534 boolean previouslyExisted, File resourceFile, String suffix )
536 if ( suffix == null )
541 // Process Create Audit Events.
542 if ( !previouslyExisted && resourceFile.exists() )
544 if ( resourceFile.isFile() )
546 triggerAuditEvent( request, resource, AuditEvent.CREATE_FILE + suffix );
548 else if ( resourceFile.isDirectory() )
550 triggerAuditEvent( request, resource, AuditEvent.CREATE_DIR + suffix );
553 // Process Remove Audit Events.
554 else if ( previouslyExisted && !resourceFile.exists() )
556 if ( resourceFile.isFile() )
558 triggerAuditEvent( request, resource, AuditEvent.REMOVE_FILE + suffix );
560 else if ( resourceFile.isDirectory() )
562 triggerAuditEvent( request, resource, AuditEvent.REMOVE_DIR + suffix );
565 // Process modify events.
568 if ( resourceFile.isFile() )
570 triggerAuditEvent( request, resource, AuditEvent.MODIFY_FILE + suffix );
575 private void triggerAuditEvent( String user, String remoteIP, String resource, String action )
577 AuditEvent event = new AuditEvent( this.getPrefix(), user, resource, action );
578 event.setRemoteIP( remoteIP );
580 for ( AuditListener listener : auditListeners )
582 listener.auditEvent( event );
586 private void triggerAuditEvent( DavServerRequest request, String resource, String action )
588 triggerAuditEvent( archivaUser.getActivePrincipal(), getRemoteIP( request ), resource,
592 private String getRemoteIP( DavServerRequest request )
594 return request.getRequest().getRemoteAddr();
597 public void addAuditListener( AuditListener listener )
599 this.auditListeners.add( listener );
602 public void clearAuditListeners()
604 this.auditListeners.clear();
607 public void removeAuditListener( AuditListener listener )
609 this.auditListeners.remove( listener );