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