]> source.dussan.org Git - archiva.git/blob
da8b31f5c90a6c9e23e3e6f744b470ea4071407e
[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.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;
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 ( ProxyDownloadException 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
395         try
396         {
397             project = metadataTools.toProjectReference( resource );
398             if ( project != null )
399             {
400                 connectors.fetchFromProxies( managedRepository, project );
401                 return true;
402             }
403         }
404         catch ( RepositoryMetadataException e )
405         {
406             /* eat it */
407         }
408
409         return false;
410     }
411
412     /**
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
416      * metadatas.
417      * <p>
418      * For such clients, archiva does server-side relocation by reading itself
419      * the &lt;relocation&gt; element in metadatas and serving the expected
420      * artifact.
421      */
422     protected void applyServerSideRelocation( ArtifactReference artifact )
423         throws ProxyDownloadException
424     {
425         if ( "pom".equals( artifact.getType() ) )
426         {
427             return;
428         }
429
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" );
436
437         // Get the artifact POM from proxied repositories if needed
438         connectors.fetchFromProxies( managedRepository, pomReference );
439
440         // Open and read the POM from the managed repo
441         File pom = managedRepository.toFile( pomReference );
442
443         if ( !pom.exists() )
444         {
445             return;
446         }
447
448         try
449         {
450             Model model = new MavenXpp3Reader().read( new FileReader( pom ) );
451             DistributionManagement dist = model.getDistributionManagement();
452             if ( dist != null )
453             {
454                 Relocation relocation = dist.getRelocation();
455                 if ( relocation != null )
456                 {
457                     // artifact is relocated : update the repositoryPath
458                     if ( relocation.getGroupId() != null )
459                     {
460                         artifact.setGroupId( relocation.getGroupId() );
461                     }
462                     if ( relocation.getArtifactId() != null )
463                     {
464                         artifact.setArtifactId( relocation.getArtifactId() );
465                     }
466                     if ( relocation.getVersion() != null )
467                     {
468                         artifact.setVersion( relocation.getVersion() );
469                     }
470                 }
471             }
472         }
473         catch ( FileNotFoundException e )
474         {
475             // Artifact has no POM in repo : ignore
476         }
477         catch ( IOException e )
478         {
479             // Unable to read POM : ignore.
480         }
481         catch ( XmlPullParserException e )
482         {
483             // Invalid POM : ignore
484         }
485     }
486
487     @Override
488     public void addListener( DavServerListener listener )
489     {
490         super.addListener( listener );
491         davServer.addListener( listener );
492     }
493
494     @Override
495     public boolean isUseIndexHtml()
496     {
497         return davServer.isUseIndexHtml();
498     }
499
500     @Override
501     public boolean hasResource( String resource )
502     {
503         return davServer.hasResource( resource );
504     }
505
506     @Override
507     public void removeListener( DavServerListener listener )
508     {
509         davServer.removeListener( listener );
510     }
511
512     @Override
513     public void setUseIndexHtml( boolean useIndexHtml )
514     {
515         super.setUseIndexHtml( useIndexHtml );
516         davServer.setUseIndexHtml( useIndexHtml );
517     }
518
519     public ManagedRepositoryContent getRepository()
520     {
521         return managedRepository;
522     }
523
524     private void processAuditEvents( DavServerRequest request, String resource,
525                                      boolean previouslyExisted, File resourceFile, String suffix )
526     {
527         if ( suffix == null )
528         {
529             suffix = "";
530         }
531
532         // Process Create Audit Events.
533         if ( !previouslyExisted && resourceFile.exists() )
534         {
535             if ( resourceFile.isFile() )
536             {
537                 triggerAuditEvent( request, resource, AuditEvent.CREATE_FILE + suffix );
538             }
539             else if ( resourceFile.isDirectory() )
540             {
541                 triggerAuditEvent( request, resource, AuditEvent.CREATE_DIR + suffix );
542             }
543         }
544         // Process Remove Audit Events.
545         else if ( previouslyExisted && !resourceFile.exists() )
546         {
547             if ( resourceFile.isFile() )
548             {
549                 triggerAuditEvent( request, resource, AuditEvent.REMOVE_FILE + suffix );
550             }
551             else if ( resourceFile.isDirectory() )
552             {
553                 triggerAuditEvent( request, resource, AuditEvent.REMOVE_DIR + suffix );
554             }
555         }
556         // Process modify events.
557         else
558         {
559             if ( resourceFile.isFile() )
560             {
561                 triggerAuditEvent( request, resource, AuditEvent.MODIFY_FILE + suffix );
562             }
563         }
564     }
565
566     private void triggerAuditEvent( String user, String remoteIP, String resource, String action )
567     {
568         AuditEvent event = new AuditEvent( this.getPrefix(), user, resource, action );
569         event.setRemoteIP( remoteIP );
570
571         for ( AuditListener listener : auditListeners )
572         {
573             listener.auditEvent( event );
574         }
575     }
576
577     private void triggerAuditEvent( DavServerRequest request, String resource, String action )
578     {
579         triggerAuditEvent( archivaUser.getActivePrincipal(), getRemoteIP( request ), resource,
580             action );
581     }
582
583     private String getRemoteIP( DavServerRequest request )
584     {
585         return request.getRequest().getRemoteAddr();
586     }
587
588     public void addAuditListener( AuditListener listener )
589     {
590         this.auditListeners.add( listener );
591     }
592
593     public void clearAuditListeners()
594     {
595         this.auditListeners.clear();
596     }
597
598     public void removeAuditListener( AuditListener listener )
599     {
600         this.auditListeners.remove( listener );
601     }
602 }