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.policies.ProxyDownloadException;
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.archiva.webdav.AbstractDavServerComponent;
41 import org.apache.maven.archiva.webdav.DavServerComponent;
42 import org.apache.maven.archiva.webdav.DavServerException;
43 import org.apache.maven.archiva.webdav.DavServerListener;
44 import org.apache.maven.archiva.webdav.servlet.DavServerRequest;
45 import org.apache.maven.archiva.webdav.util.WebdavMethodUtil;
46 import org.apache.maven.model.DistributionManagement;
47 import org.apache.maven.model.Model;
48 import org.apache.maven.model.Relocation;
49 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
50 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
52 import javax.servlet.ServletConfig;
53 import javax.servlet.ServletException;
54 import javax.servlet.http.HttpServletResponse;
56 import java.io.FileNotFoundException;
57 import java.io.FileReader;
58 import java.io.IOException;
59 import java.io.PrintWriter;
60 import java.util.ArrayList;
61 import java.util.List;
66 * @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
68 * @plexus.component role="org.apache.maven.archiva.webdav.DavServerComponent"
69 * role-hint="proxied" instantiation-strategy="per-lookup"
71 public class ProxiedDavServer
72 extends AbstractDavServerComponent
76 * @plexus.requirement role-hint="simple"
78 private DavServerComponent davServer;
81 * @plexus.requirement role="org.apache.maven.archiva.repository.audit.AuditListener"
83 private List<AuditListener> auditListeners = new ArrayList<AuditListener>();
88 private RepositoryContentFactory repositoryFactory;
93 private RepositoryRequest repositoryRequest;
96 * @plexus.requirement role-hint="default"
98 private RepositoryProxyConnectors connectors;
101 * @plexus.requirement
103 private MetadataTools metadataTools;
106 * @plexus.requirement role-hint="xwork"
108 private ArchivaUser archivaUser;
110 private ManagedRepositoryContent managedRepository;
112 public String getPrefix()
114 return davServer.getPrefix();
117 public File getRootDirectory()
119 return davServer.getRootDirectory();
122 public void setPrefix( String prefix )
124 davServer.setPrefix( prefix );
127 public void setRootDirectory( File rootDirectory )
129 davServer.setRootDirectory( rootDirectory );
132 public void init( ServletConfig servletConfig )
133 throws DavServerException
135 davServer.init( servletConfig );
139 managedRepository = repositoryFactory.getManagedRepositoryContent( getPrefix() );
141 catch ( RepositoryNotFoundException e )
143 throw new DavServerException( e.getMessage(), e );
145 catch ( RepositoryException e )
147 throw new DavServerException( e.getMessage(), e );
151 public void process( DavServerRequest request, HttpServletResponse response )
152 throws DavServerException, ServletException, IOException
154 boolean isGet = WebdavMethodUtil.isReadMethod( request.getRequest().getMethod() );
155 boolean isPut = WebdavMethodUtil.isWriteMethod( request.getRequest().getMethod() );
156 String resource = request.getLogicalResource();
160 // Default behaviour is to treat the resource natively.
161 File resourceFile = new File( managedRepository.getRepoRoot(), resource );
163 // If this a directory resource, then we are likely browsing.
164 if ( resourceFile.exists() && resourceFile.isDirectory() )
166 String requestURL = request.getRequest().getRequestURL().toString();
168 // [MRM-440] - If webdav URL lacks a trailing /, navigating to
169 // 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
185 // legacy layout format.
188 boolean fromProxy = fetchContentFromProxies( request, resource );
190 // Perform an adjustment of the resource to the managed
191 // repository expected path.
194 .toNativePath( request.getLogicalResource(), managedRepository );
195 resourceFile = new File( managedRepository.getRepoRoot(), resource );
197 // Adjust the pathInfo resource to be in the format that the dav
198 // server impl expects.
199 request.setLogicalResource( resource );
201 boolean previouslyExisted = resourceFile.exists();
203 // Attempt to fetch the resource from any defined proxy.
206 processAuditEvents( request, resource, previouslyExisted, resourceFile,
210 catch ( LayoutException e )
212 // Invalid resource, pass it on.
213 respondResourceMissing( request, response, e );
219 if ( resourceFile.exists() )
221 // [MRM-503] - Metadata file need Pragma:no-cache response
223 if ( request.getLogicalResource().endsWith( "/maven-metadata.xml" ) )
225 response.addHeader( "Pragma", "no-cache" );
226 response.addHeader( "Cache-Control", "no-cache" );
229 // TODO: [MRM-524] determine http caching options for other
230 // types of files (artifacts, sha1, md5, snapshots)
232 davServer.process( request, response );
236 respondResourceMissing( request, response, null );
243 * Create parent directories that don't exist when writing a file
244 * This actually makes this implementation not compliant to the
245 * WebDAV RFC - but we have enough knowledge about how the
246 * collection is being used to do this reasonably and some versions
247 * of Maven's WebDAV don't correctly create the collections
251 File rootDirectory = getRootDirectory();
252 if ( rootDirectory != null )
254 File destDir = new File( rootDirectory, resource ).getParentFile();
255 if ( !destDir.exists() )
259 PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
260 triggerAuditEvent( request, relPath, AuditEvent.CREATE_DIR );
264 File resourceFile = new File( managedRepository.getRepoRoot(), resource );
266 boolean previouslyExisted = resourceFile.exists();
268 // Allow the dav server to process the put request.
269 davServer.process( request, response );
271 processAuditEvents( request, resource, previouslyExisted, resourceFile, null );
278 private void respondResourceMissing( DavServerRequest request, HttpServletResponse response,
281 response.setStatus( HttpServletResponse.SC_NOT_FOUND );
285 StringBuffer missingUrl = new StringBuffer();
286 missingUrl.append( request.getRequest().getScheme() ).append( "://" );
287 missingUrl.append( request.getRequest().getServerName() ).append( ":" );
288 missingUrl.append( request.getRequest().getServerPort() );
289 missingUrl.append( request.getRequest().getServletPath() );
291 String message = "Error 404 Not Found";
293 PrintWriter out = new PrintWriter( response.getOutputStream() );
295 response.setContentType( "text/html; charset=\"UTF-8\"" );
297 out.println( "<html>" );
298 out.println( "<head><title>" + message + "</title></head>" );
299 out.println( "<body>" );
301 out.print( "<p><h1>" );
302 out.print( message );
303 out.println( "</h1></p>" );
305 out.print( "<p>The following resource does not exist: <a href=\"" );
306 out.print( missingUrl.toString() );
307 out.println( "\">" );
308 out.print( missingUrl.toString() );
309 out.println( "</a></p>" );
313 out.println( "<pre>" );
314 t.printStackTrace( out );
315 out.println( "</pre>" );
318 out.println( "</body></html>" );
322 catch ( IOException e )
328 private boolean fetchContentFromProxies( DavServerRequest request, String resource )
329 throws ServletException
331 if ( repositoryRequest.isSupportFile( resource ) )
333 // Checksums are fetched with artifact / metadata.
335 // Need to adjust the path for the checksum resource.
339 // Is it a Metadata resource?
340 if ( repositoryRequest.isDefault( resource ) && repositoryRequest.isMetadata( resource ) )
342 return fetchMetadataFromProxies( request, resource );
345 // Not any of the above? Then it's gotta be an artifact reference.
348 // Get the artifact reference in a layout neutral way.
349 ArtifactReference artifact = repositoryRequest.toArtifactReference( resource );
351 if ( artifact != null )
353 applyServerSideRelocation( artifact );
355 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
357 // Set the path to the resource using managed repository
358 // specific layout format.
359 request.setLogicalResource( managedRepository.toPath( artifact ) );
360 return ( proxiedFile != null );
363 catch ( LayoutException e )
367 catch ( ProxyDownloadException e )
369 throw new ServletException( "Unable to fetch artifact resource.", e );
374 private boolean fetchMetadataFromProxies( DavServerRequest request, String resource )
375 throws ServletException
377 ProjectReference project;
378 VersionedReference versioned;
383 versioned = metadataTools.toVersionedReference( resource );
384 if ( versioned != null )
386 connectors.fetchFromProxies( managedRepository, versioned );
390 catch ( RepositoryMetadataException e )
397 project = metadataTools.toProjectReference( resource );
398 if ( project != null )
400 connectors.fetchFromProxies( managedRepository, project );
404 catch ( RepositoryMetadataException e )
413 * A relocation capable client will request the POM prior to the artifact,
414 * and will then read meta-data and do client side relocation. A simplier
415 * client (like maven 1) will only request the artifact and not use the
418 * For such clients, archiva does server-side relocation by reading itself
419 * the <relocation> element in metadatas and serving the expected
422 protected void applyServerSideRelocation( ArtifactReference artifact )
423 throws ProxyDownloadException
425 if ( "pom".equals( artifact.getType() ) )
430 // Build the artifact POM reference
431 ArtifactReference pomReference = new ArtifactReference();
432 pomReference.setGroupId( artifact.getGroupId() );
433 pomReference.setArtifactId( artifact.getArtifactId() );
434 pomReference.setVersion( artifact.getVersion() );
435 pomReference.setType( "pom" );
437 // Get the artifact POM from proxied repositories if needed
438 connectors.fetchFromProxies( managedRepository, pomReference );
440 // Open and read the POM from the managed repo
441 File pom = managedRepository.toFile( pomReference );
450 Model model = new MavenXpp3Reader().read( new FileReader( pom ) );
451 DistributionManagement dist = model.getDistributionManagement();
454 Relocation relocation = dist.getRelocation();
455 if ( relocation != null )
457 // artifact is relocated : update the repositoryPath
458 if ( relocation.getGroupId() != null )
460 artifact.setGroupId( relocation.getGroupId() );
462 if ( relocation.getArtifactId() != null )
464 artifact.setArtifactId( relocation.getArtifactId() );
466 if ( relocation.getVersion() != null )
468 artifact.setVersion( relocation.getVersion() );
473 catch ( FileNotFoundException e )
475 // Artifact has no POM in repo : ignore
477 catch ( IOException e )
479 // Unable to read POM : ignore.
481 catch ( XmlPullParserException e )
483 // Invalid POM : ignore
488 public void addListener( DavServerListener listener )
490 super.addListener( listener );
491 davServer.addListener( listener );
495 public boolean isUseIndexHtml()
497 return davServer.isUseIndexHtml();
501 public boolean hasResource( String resource )
503 return davServer.hasResource( resource );
507 public void removeListener( DavServerListener listener )
509 davServer.removeListener( listener );
513 public void setUseIndexHtml( boolean useIndexHtml )
515 super.setUseIndexHtml( useIndexHtml );
516 davServer.setUseIndexHtml( useIndexHtml );
519 public ManagedRepositoryContent getRepository()
521 return managedRepository;
524 private void processAuditEvents( DavServerRequest request, String resource,
525 boolean previouslyExisted, File resourceFile, String suffix )
527 if ( suffix == null )
532 // Process Create Audit Events.
533 if ( !previouslyExisted && resourceFile.exists() )
535 if ( resourceFile.isFile() )
537 triggerAuditEvent( request, resource, AuditEvent.CREATE_FILE + suffix );
539 else if ( resourceFile.isDirectory() )
541 triggerAuditEvent( request, resource, AuditEvent.CREATE_DIR + suffix );
544 // Process Remove Audit Events.
545 else if ( previouslyExisted && !resourceFile.exists() )
547 if ( resourceFile.isFile() )
549 triggerAuditEvent( request, resource, AuditEvent.REMOVE_FILE + suffix );
551 else if ( resourceFile.isDirectory() )
553 triggerAuditEvent( request, resource, AuditEvent.REMOVE_DIR + suffix );
556 // Process modify events.
559 if ( resourceFile.isFile() )
561 triggerAuditEvent( request, resource, AuditEvent.MODIFY_FILE + suffix );
566 private void triggerAuditEvent( String user, String remoteIP, String resource, String action )
568 AuditEvent event = new AuditEvent( this.getPrefix(), user, resource, action );
569 event.setRemoteIP( remoteIP );
571 for ( AuditListener listener : auditListeners )
573 listener.auditEvent( event );
577 private void triggerAuditEvent( DavServerRequest request, String resource, String action )
579 triggerAuditEvent( archivaUser.getActivePrincipal(), getRemoteIP( request ), resource,
583 private String getRemoteIP( DavServerRequest request )
585 return request.getRequest().getRemoteAddr();
588 public void addAuditListener( AuditListener listener )
590 this.auditListeners.add( listener );
593 public void clearAuditListeners()
595 this.auditListeners.clear();
598 public void removeAuditListener( AuditListener listener )
600 this.auditListeners.remove( listener );