]> source.dussan.org Git - archiva.git/blob
027f58f54b3a898f02b57c500fabbf224f4e8e08
[archiva.git] /
1 package org.apache.maven.archiva.web.repository;
2
3 /*
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
11  *
12  *  http://www.apache.org/licenses/LICENSE-2.0
13  *
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
19  * under the License.
20  */
21
22 import java.io.File;
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;
29
30 import javax.servlet.ServletConfig;
31 import javax.servlet.ServletException;
32 import javax.servlet.http.HttpServletResponse;
33
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;
63
64 /**
65  * ProxiedDavServer
66  * 
67  * @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
68  * @version $Id$
69  * @plexus.component role="org.codehaus.plexus.webdav.DavServerComponent"
70  * role-hint="proxied" instantiation-strategy="per-lookup"
71  */
72 public class ProxiedDavServer
73     extends AbstractDavServerComponent
74     implements Auditable
75 {
76     /**
77      * @plexus.requirement role-hint="simple"
78      */
79     private DavServerComponent davServer;
80
81     /**
82      * @plexus.requirement role="org.apache.maven.archiva.repository.audit.AuditListener"
83      */
84     private List<AuditListener> auditListeners = new ArrayList<AuditListener>();
85
86     /**
87      * @plexus.requirement
88      */
89     private RepositoryContentFactory repositoryFactory;
90
91     /**
92      * @plexus.requirement
93      */
94     private RepositoryRequest repositoryRequest;
95
96     /**
97      * @plexus.requirement role-hint="default"
98      */
99     private RepositoryProxyConnectors connectors;
100
101     /**
102      * @plexus.requirement
103      */
104     private MetadataTools metadataTools;
105
106     /**
107      * @plexus.requirement role-hint="xwork"
108      */
109     private ArchivaUser archivaUser;
110
111     private ManagedRepositoryContent managedRepository;
112
113     public String getPrefix()
114     {
115         return davServer.getPrefix();
116     }
117
118     public File getRootDirectory()
119     {
120         return davServer.getRootDirectory();
121     }
122
123     public void setPrefix( String prefix )
124     {
125         davServer.setPrefix( prefix );
126     }
127
128     public void setRootDirectory( File rootDirectory )
129     {
130         davServer.setRootDirectory( rootDirectory );
131     }
132
133     public void init( ServletConfig servletConfig )
134         throws DavServerException
135     {
136         davServer.init( servletConfig );
137
138         try
139         {
140             managedRepository = repositoryFactory.getManagedRepositoryContent( getPrefix() );
141         }
142         catch ( RepositoryNotFoundException e )
143         {
144             throw new DavServerException( e.getMessage(), e );
145         }
146         catch ( RepositoryException e )
147         {
148             throw new DavServerException( e.getMessage(), e );
149         }
150     }
151
152     public void process( DavServerRequest request, HttpServletResponse response )
153         throws DavServerException, ServletException, IOException
154     {
155         boolean isGet = WebdavMethodUtil.isReadMethod( request.getRequest().getMethod() );
156         boolean isPut = WebdavMethodUtil.isWriteMethod( request.getRequest().getMethod() );
157         String resource = request.getLogicalResource();
158
159         if ( isGet )
160         {
161             // Default behaviour is to treat the resource natively.
162             File resourceFile = new File( managedRepository.getRepoRoot(), resource );
163
164             // If this a directory resource, then we are likely browsing.
165             if ( resourceFile.exists() && resourceFile.isDirectory() )
166             {
167                 String requestURL = request.getRequest().getRequestURL().toString();
168
169                 // [MRM-440] - If webdav URL lacks a trailing /, navigating to
170                 // all links in the listing return 404.
171                 if ( !requestURL.endsWith( "/" ) )
172                 {
173                     String redirectToLocation = requestURL + "/";
174                     response.sendRedirect( redirectToLocation );
175                     return;
176                 }
177
178                 // Process the request.
179                 davServer.process( request, response );
180
181                 // All done.
182                 return;
183             }
184
185             // At this point the incoming request can either be in default or
186             // legacy layout format.
187             try
188             {
189                 boolean fromProxy = fetchContentFromProxies( request, resource );
190
191                 // Perform an adjustment of the resource to the managed
192                 // repository expected path.
193                 resource =
194                     repositoryRequest
195                         .toNativePath( request.getLogicalResource(), managedRepository );
196                 resourceFile = new File( managedRepository.getRepoRoot(), resource );                
197
198                 // Adjust the pathInfo resource to be in the format that the dav
199                 // server impl expects.
200                 request.setLogicalResource( resource );
201
202                 boolean previouslyExisted = resourceFile.exists();
203
204                 // Attempt to fetch the resource from any defined proxy.
205                 if ( fromProxy )
206                 {
207                     processAuditEvents( request, resource, previouslyExisted, resourceFile,
208                         " (proxied)" );
209                 }
210             }
211             catch ( LayoutException e )
212             {
213                 // Invalid resource, pass it on.
214                 respondResourceMissing( request, response, e );
215
216                 // All done.
217                 return;
218             }
219
220             if ( resourceFile.exists() )
221             {
222                 // [MRM-503] - Metadata file need Pragma:no-cache response
223                 // header.
224                 if ( request.getLogicalResource().endsWith( "/maven-metadata.xml" ) )
225                 {
226                     response.addHeader( "Pragma", "no-cache" );
227                     response.addHeader( "Cache-Control", "no-cache" );
228                 }
229
230                 // TODO: [MRM-524] determine http caching options for other
231                 // types of files (artifacts, sha1, md5, snapshots)
232
233                 davServer.process( request, response );
234             }
235             else
236             {
237                 respondResourceMissing( request, response, null );
238             }
239         }
240
241         if ( isPut )
242         {
243             /*
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
249              * themselves.
250              */
251
252             File rootDirectory = getRootDirectory();
253             if ( rootDirectory != null )
254             {
255                 File destDir = new File( rootDirectory, resource ).getParentFile();
256                 if ( !destDir.exists() )
257                 {
258                     destDir.mkdirs();
259                     String relPath =
260                         PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
261                     triggerAuditEvent( request, relPath, AuditEvent.CREATE_DIR );
262                 }
263             }
264
265             File resourceFile = new File( managedRepository.getRepoRoot(), resource );
266
267             boolean previouslyExisted = resourceFile.exists();
268
269             // Allow the dav server to process the put request.
270             davServer.process( request, response );
271
272             processAuditEvents( request, resource, previouslyExisted, resourceFile, null );
273
274             // All done.
275             return;
276         }
277     }
278
279     private void respondResourceMissing( DavServerRequest request, HttpServletResponse response,
280                                          Throwable t )
281     {
282         response.setStatus( HttpServletResponse.SC_NOT_FOUND );
283
284         try
285         {
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() );
291
292             String message = "Error 404 Not Found";
293
294             PrintWriter out = new PrintWriter( response.getOutputStream() );
295
296             response.setContentType( "text/html; charset=\"UTF-8\"" );
297
298             out.println( "<html>" );
299             out.println( "<head><title>" + message + "</title></head>" );
300             out.println( "<body>" );
301
302             out.print( "<p><h1>" );
303             out.print( message );
304             out.println( "</h1></p>" );
305
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>" );
311
312             if ( t != null )
313             {
314                 out.println( "<pre>" );
315                 t.printStackTrace( out );
316                 out.println( "</pre>" );
317             }
318
319             out.println( "</body></html>" );
320
321             out.flush();
322         }
323         catch ( IOException e )
324         {
325             e.printStackTrace();
326         }
327     }
328
329     private boolean fetchContentFromProxies( DavServerRequest request, String resource )
330         throws ServletException
331     {
332         if ( repositoryRequest.isSupportFile( resource ) )
333         {
334             // Checksums are fetched with artifact / metadata.
335
336             // Need to adjust the path for the checksum resource.
337             return false;
338         }
339
340         // Is it a Metadata resource?
341         if ( repositoryRequest.isDefault( resource ) && repositoryRequest.isMetadata( resource ) )
342         {
343             return fetchMetadataFromProxies( request, resource );
344         }
345
346         // Not any of the above? Then it's gotta be an artifact reference.
347         try
348         {
349             // Get the artifact reference in a layout neutral way.
350             ArtifactReference artifact = repositoryRequest.toArtifactReference( resource );
351
352             if ( artifact != null )
353             {
354                 applyServerSideRelocation( artifact );
355
356                 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
357
358                 // Set the path to the resource using managed repository
359                 // specific layout format.
360                 request.setLogicalResource( managedRepository.toPath( artifact ) );
361                 return ( proxiedFile != null );
362             }
363         }
364         catch ( LayoutException e )
365         {
366             /* eat it */
367         }
368         catch ( ProxyException e )
369         {
370             throw new ServletException( "Unable to fetch artifact resource.", e );
371         }
372         return false;
373     }
374
375     private boolean fetchMetadataFromProxies( DavServerRequest request, String resource )
376         throws ServletException
377     {
378         ProjectReference project;
379         VersionedReference versioned;
380
381         try
382         {
383
384             versioned = metadataTools.toVersionedReference( resource );
385             if ( versioned != null )
386             {
387                 connectors.fetchFromProxies( managedRepository, versioned );
388                 return true;
389             }
390         }
391         catch ( RepositoryMetadataException e )
392         {
393             /* eat it */
394         }
395         catch ( ProxyException e )
396         {
397             throw new ServletException( "Unable to fetch versioned metadata resource.", e );
398         }
399
400         try
401         {
402             project = metadataTools.toProjectReference( resource );
403             if ( project != null )
404             {
405                 connectors.fetchFromProxies( managedRepository, project );
406                 return true;
407             }
408         }
409         catch ( RepositoryMetadataException e )
410         {
411             /* eat it */
412         }
413         catch ( ProxyException e )
414         {
415             throw new ServletException( "Unable to fetch project metadata resource.", e );
416         }
417
418         return false;
419     }
420
421     /**
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
425      * metadatas.
426      * <p>
427      * For such clients, archiva does server-side relocation by reading itself
428      * the &lt;relocation&gt; element in metadatas and serving the expected
429      * artifact.
430      */
431     protected void applyServerSideRelocation( ArtifactReference artifact )
432         throws ProxyException
433     {
434         if ( "pom".equals( artifact.getType() ) )
435         {
436             return;
437         }
438
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" );
445
446         // Get the artifact POM from proxied repositories if needed
447         connectors.fetchFromProxies( managedRepository, pomReference );
448
449         // Open and read the POM from the managed repo
450         File pom = managedRepository.toFile( pomReference );
451
452         if ( !pom.exists() )
453         {
454             return;
455         }
456
457         try
458         {
459             Model model = new MavenXpp3Reader().read( new FileReader( pom ) );
460             DistributionManagement dist = model.getDistributionManagement();
461             if ( dist != null )
462             {
463                 Relocation relocation = dist.getRelocation();
464                 if ( relocation != null )
465                 {
466                     // artifact is relocated : update the repositoryPath
467                     if ( relocation.getGroupId() != null )
468                     {
469                         artifact.setGroupId( relocation.getGroupId() );
470                     }
471                     if ( relocation.getArtifactId() != null )
472                     {
473                         artifact.setArtifactId( relocation.getArtifactId() );
474                     }
475                     if ( relocation.getVersion() != null )
476                     {
477                         artifact.setVersion( relocation.getVersion() );
478                     }
479                 }
480             }
481         }
482         catch ( FileNotFoundException e )
483         {
484             // Artifact has no POM in repo : ignore
485         }
486         catch ( IOException e )
487         {
488             // Unable to read POM : ignore.
489         }
490         catch ( XmlPullParserException e )
491         {
492             // Invalid POM : ignore
493         }
494     }
495
496     @Override
497     public void addListener( DavServerListener listener )
498     {
499         super.addListener( listener );
500         davServer.addListener( listener );
501     }
502
503     @Override
504     public boolean isUseIndexHtml()
505     {
506         return davServer.isUseIndexHtml();
507     }
508
509     @Override
510     public boolean hasResource( String resource )
511     {
512         return davServer.hasResource( resource );
513     }
514
515     @Override
516     public void removeListener( DavServerListener listener )
517     {
518         davServer.removeListener( listener );
519     }
520
521     @Override
522     public void setUseIndexHtml( boolean useIndexHtml )
523     {
524         super.setUseIndexHtml( useIndexHtml );
525         davServer.setUseIndexHtml( useIndexHtml );
526     }
527
528     public ManagedRepositoryContent getRepository()
529     {
530         return managedRepository;
531     }
532
533     private void processAuditEvents( DavServerRequest request, String resource,
534                                      boolean previouslyExisted, File resourceFile, String suffix )
535     {
536         if ( suffix == null )
537         {
538             suffix = "";
539         }
540
541         // Process Create Audit Events.
542         if ( !previouslyExisted && resourceFile.exists() )
543         {
544             if ( resourceFile.isFile() )
545             {
546                 triggerAuditEvent( request, resource, AuditEvent.CREATE_FILE + suffix );
547             }
548             else if ( resourceFile.isDirectory() )
549             {
550                 triggerAuditEvent( request, resource, AuditEvent.CREATE_DIR + suffix );
551             }
552         }
553         // Process Remove Audit Events.
554         else if ( previouslyExisted && !resourceFile.exists() )
555         {
556             if ( resourceFile.isFile() )
557             {
558                 triggerAuditEvent( request, resource, AuditEvent.REMOVE_FILE + suffix );
559             }
560             else if ( resourceFile.isDirectory() )
561             {
562                 triggerAuditEvent( request, resource, AuditEvent.REMOVE_DIR + suffix );
563             }
564         }
565         // Process modify events.
566         else
567         {
568             if ( resourceFile.isFile() )
569             {
570                 triggerAuditEvent( request, resource, AuditEvent.MODIFY_FILE + suffix );
571             }
572         }
573     }
574
575     private void triggerAuditEvent( String user, String remoteIP, String resource, String action )
576     {
577         AuditEvent event = new AuditEvent( this.getPrefix(), user, resource, action );
578         event.setRemoteIP( remoteIP );
579
580         for ( AuditListener listener : auditListeners )
581         {
582             listener.auditEvent( event );
583         }
584     }
585
586     private void triggerAuditEvent( DavServerRequest request, String resource, String action )
587     {
588         triggerAuditEvent( archivaUser.getActivePrincipal(), getRemoteIP( request ), resource,
589             action );
590     }
591
592     private String getRemoteIP( DavServerRequest request )
593     {
594         return request.getRequest().getRemoteAddr();
595     }
596
597     public void addAuditListener( AuditListener listener )
598     {
599         this.auditListeners.add( listener );
600     }
601
602     public void clearAuditListeners()
603     {
604         this.auditListeners.clear();
605     }
606
607     public void removeAuditListener( AuditListener listener )
608     {
609         this.auditListeners.remove( listener );
610     }
611 }