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.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 ( ProxyException 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 )
394 catch ( ProxyException e )
396 throw new ServletException( "Unable to fetch versioned metadata resource.", e );
401 project = metadataTools.toProjectReference( resource );
402 if ( project != null )
404 connectors.fetchFromProxies( managedRepository, project );
408 catch ( RepositoryMetadataException e )
412 catch ( ProxyException e )
414 throw new ServletException( "Unable to fetch project metadata resource.", e );
421 * A relocation capable client will request the POM prior to the artifact,
422 * and will then read meta-data and do client side relocation. A simplier
423 * client (like maven 1) will only request the artifact and not use the
426 * For such clients, archiva does server-side relocation by reading itself
427 * the <relocation> element in metadatas and serving the expected
430 protected void applyServerSideRelocation( ArtifactReference artifact )
431 throws ProxyException
433 if ( "pom".equals( artifact.getType() ) )
438 // Build the artifact POM reference
439 ArtifactReference pomReference = new ArtifactReference();
440 pomReference.setGroupId( artifact.getGroupId() );
441 pomReference.setArtifactId( artifact.getArtifactId() );
442 pomReference.setVersion( artifact.getVersion() );
443 pomReference.setType( "pom" );
445 // Get the artifact POM from proxied repositories if needed
446 connectors.fetchFromProxies( managedRepository, pomReference );
448 // Open and read the POM from the managed repo
449 File pom = managedRepository.toFile( pomReference );
458 Model model = new MavenXpp3Reader().read( new FileReader( pom ) );
459 DistributionManagement dist = model.getDistributionManagement();
462 Relocation relocation = dist.getRelocation();
463 if ( relocation != null )
465 // artifact is relocated : update the repositoryPath
466 if ( relocation.getGroupId() != null )
468 artifact.setGroupId( relocation.getGroupId() );
470 if ( relocation.getArtifactId() != null )
472 artifact.setArtifactId( relocation.getArtifactId() );
474 if ( relocation.getVersion() != null )
476 artifact.setVersion( relocation.getVersion() );
481 catch ( FileNotFoundException e )
483 // Artifact has no POM in repo : ignore
485 catch ( IOException e )
487 // Unable to read POM : ignore.
489 catch ( XmlPullParserException e )
491 // Invalid POM : ignore
496 public void addListener( DavServerListener listener )
498 super.addListener( listener );
499 davServer.addListener( listener );
503 public boolean isUseIndexHtml()
505 return davServer.isUseIndexHtml();
509 public boolean hasResource( String resource )
511 return davServer.hasResource( resource );
515 public void removeListener( DavServerListener listener )
517 davServer.removeListener( listener );
521 public void setUseIndexHtml( boolean useIndexHtml )
523 super.setUseIndexHtml( useIndexHtml );
524 davServer.setUseIndexHtml( useIndexHtml );
527 public ManagedRepositoryContent getRepository()
529 return managedRepository;
532 private void processAuditEvents( DavServerRequest request, String resource,
533 boolean previouslyExisted, File resourceFile, String suffix )
535 if ( suffix == null )
540 // Process Create Audit Events.
541 if ( !previouslyExisted && resourceFile.exists() )
543 if ( resourceFile.isFile() )
545 triggerAuditEvent( request, resource, AuditEvent.CREATE_FILE + suffix );
547 else if ( resourceFile.isDirectory() )
549 triggerAuditEvent( request, resource, AuditEvent.CREATE_DIR + suffix );
552 // Process Remove Audit Events.
553 else if ( previouslyExisted && !resourceFile.exists() )
555 if ( resourceFile.isFile() )
557 triggerAuditEvent( request, resource, AuditEvent.REMOVE_FILE + suffix );
559 else if ( resourceFile.isDirectory() )
561 triggerAuditEvent( request, resource, AuditEvent.REMOVE_DIR + suffix );
564 // Process modify events.
567 if ( resourceFile.isFile() )
569 triggerAuditEvent( request, resource, AuditEvent.MODIFY_FILE + suffix );
574 private void triggerAuditEvent( String user, String remoteIP, String resource, String action )
576 AuditEvent event = new AuditEvent( this.getPrefix(), user, resource, action );
577 event.setRemoteIP( remoteIP );
579 for ( AuditListener listener : auditListeners )
581 listener.auditEvent( event );
585 private void triggerAuditEvent( DavServerRequest request, String resource, String action )
587 triggerAuditEvent( archivaUser.getActivePrincipal(), getRemoteIP( request ), resource,
591 private String getRemoteIP( DavServerRequest request )
593 return request.getRequest().getRemoteAddr();
596 public void addAuditListener( AuditListener listener )
598 this.auditListeners.add( listener );
601 public void clearAuditListeners()
603 this.auditListeners.clear();
606 public void removeAuditListener( AuditListener listener )
608 this.auditListeners.remove( listener );