]> source.dussan.org Git - archiva.git/blob
9d4b8ce92f01f2b2d8c90b7386319aaaf078a845
[archiva.git] /
1 package org.apache.archiva.webdav;
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.archiva.admin.model.RepositoryAdminException;
23 import org.apache.archiva.admin.model.beans.ManagedRepository;
24 import org.apache.archiva.admin.model.beans.RemoteRepository;
25 import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin;
26 import org.apache.archiva.admin.model.remote.RemoteRepositoryAdmin;
27 import org.apache.archiva.audit.Auditable;
28 import org.apache.archiva.common.filelock.FileLockManager;
29 import org.apache.archiva.common.plexusbridge.PlexusSisuBridge;
30 import org.apache.archiva.common.plexusbridge.PlexusSisuBridgeException;
31 import org.apache.archiva.common.utils.PathUtil;
32 import org.apache.archiva.common.utils.VersionUtil;
33 import org.apache.archiva.configuration.ArchivaConfiguration;
34 import org.apache.archiva.configuration.RepositoryGroupConfiguration;
35 import org.apache.archiva.indexer.merger.IndexMerger;
36 import org.apache.archiva.indexer.merger.IndexMergerException;
37 import org.apache.archiva.indexer.merger.IndexMergerRequest;
38 import org.apache.archiva.indexer.merger.MergedRemoteIndexesTask;
39 import org.apache.archiva.indexer.merger.MergedRemoteIndexesTaskRequest;
40 import org.apache.archiva.indexer.merger.TemporaryGroupIndex;
41 import org.apache.archiva.indexer.search.RepositorySearch;
42 import org.apache.archiva.maven2.metadata.MavenMetadataReader;
43 import org.apache.archiva.metadata.model.facets.AuditEvent;
44 import org.apache.archiva.metadata.repository.storage.RelocationException;
45 import org.apache.archiva.metadata.repository.storage.RepositoryStorage;
46 import org.apache.archiva.model.ArchivaRepositoryMetadata;
47 import org.apache.archiva.model.ArtifactReference;
48 import org.apache.archiva.policies.ProxyDownloadException;
49 import org.apache.archiva.proxy.model.RepositoryProxyConnectors;
50 import org.apache.archiva.redback.authentication.AuthenticationException;
51 import org.apache.archiva.redback.authentication.AuthenticationResult;
52 import org.apache.archiva.redback.authorization.AuthorizationException;
53 import org.apache.archiva.redback.authorization.UnauthorizedException;
54 import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticator;
55 import org.apache.archiva.redback.policy.AccountLockedException;
56 import org.apache.archiva.redback.policy.MustChangePasswordException;
57 import org.apache.archiva.redback.system.SecuritySession;
58 import org.apache.archiva.redback.users.User;
59 import org.apache.archiva.redback.users.UserManager;
60 import org.apache.archiva.repository.ManagedRepositoryContent;
61 import org.apache.archiva.repository.RepositoryContentFactory;
62 import org.apache.archiva.repository.RepositoryException;
63 import org.apache.archiva.repository.RepositoryNotFoundException;
64 import org.apache.archiva.repository.content.legacy.LegacyPathParser;
65 import org.apache.archiva.repository.content.maven2.RepositoryRequest;
66 import org.apache.archiva.repository.events.AuditListener;
67 import org.apache.archiva.repository.layout.LayoutException;
68 import org.apache.archiva.repository.metadata.MetadataTools;
69 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
70 import org.apache.archiva.repository.metadata.RepositoryMetadataMerge;
71 import org.apache.archiva.repository.metadata.RepositoryMetadataWriter;
72 import org.apache.archiva.scheduler.repository.model.RepositoryArchivaTaskScheduler;
73 import org.apache.archiva.security.ServletAuthenticator;
74 import org.apache.archiva.webdav.util.MimeTypes;
75 import org.apache.archiva.webdav.util.TemporaryGroupIndexSessionCleaner;
76 import org.apache.archiva.webdav.util.WebdavMethodUtil;
77 import org.apache.archiva.xml.XMLException;
78 import org.apache.commons.io.FileUtils;
79 import org.apache.commons.io.FilenameUtils;
80 import org.apache.commons.lang.StringUtils;
81 import org.apache.commons.lang.SystemUtils;
82 import org.apache.jackrabbit.webdav.DavException;
83 import org.apache.jackrabbit.webdav.DavResource;
84 import org.apache.jackrabbit.webdav.DavResourceFactory;
85 import org.apache.jackrabbit.webdav.DavResourceLocator;
86 import org.apache.jackrabbit.webdav.DavServletRequest;
87 import org.apache.jackrabbit.webdav.DavServletResponse;
88 import org.apache.jackrabbit.webdav.DavSession;
89 import org.apache.jackrabbit.webdav.lock.LockManager;
90 import org.apache.jackrabbit.webdav.lock.SimpleLockManager;
91 import org.apache.maven.index.context.IndexingContext;
92 import org.codehaus.plexus.digest.ChecksumFile;
93 import org.codehaus.plexus.digest.Digester;
94 import org.codehaus.plexus.digest.DigesterException;
95 import org.slf4j.Logger;
96 import org.slf4j.LoggerFactory;
97 import org.slf4j.MarkerFactory;
98 import org.springframework.context.ApplicationContext;
99 import org.springframework.stereotype.Service;
100
101 import javax.annotation.PostConstruct;
102 import javax.inject.Inject;
103 import javax.inject.Named;
104 import javax.servlet.http.HttpServletResponse;
105 import javax.servlet.http.HttpSession;
106 import java.io.File;
107 import java.io.IOException;
108 import java.nio.file.Files;
109 import java.util.ArrayList;
110 import java.util.Date;
111 import java.util.HashMap;
112 import java.util.HashSet;
113 import java.util.List;
114 import java.util.Map;
115 import java.util.Set;
116
117 /**
118  *
119  */
120 @Service( "davResourceFactory#archiva" )
121 public class ArchivaDavResourceFactory
122     implements DavResourceFactory, Auditable
123 {
124     private static final String PROXIED_SUFFIX = " (proxied)";
125
126     private static final String HTTP_PUT_METHOD = "PUT";
127
128     private Logger log = LoggerFactory.getLogger( ArchivaDavResourceFactory.class );
129
130     @Inject
131     private List<AuditListener> auditListeners = new ArrayList<>();
132
133     @Inject
134     private RepositoryContentFactory repositoryFactory;
135
136     private RepositoryRequest repositoryRequest;
137
138     @Inject
139     @Named( value = "repositoryProxyConnectors#default" )
140     private RepositoryProxyConnectors connectors;
141
142     @Inject
143     private MetadataTools metadataTools;
144
145     @Inject
146     private MimeTypes mimeTypes;
147
148     private ArchivaConfiguration archivaConfiguration;
149
150     @Inject
151     private ServletAuthenticator servletAuth;
152
153     @Inject
154     @Named( value = "httpAuthenticator#basic" )
155     private HttpAuthenticator httpAuth;
156
157     @Inject
158     private RemoteRepositoryAdmin remoteRepositoryAdmin;
159
160     @Inject
161     private ManagedRepositoryAdmin managedRepositoryAdmin;
162
163     @Inject
164     private IndexMerger indexMerger;
165
166     @Inject
167     private RepositorySearch repositorySearch;
168
169     /**
170      * Lock Manager - use simple implementation from JackRabbit
171      */
172     private final LockManager lockManager = new SimpleLockManager();
173
174     private ChecksumFile checksum;
175
176     private Digester digestSha1;
177
178     private Digester digestMd5;
179
180     @Inject
181     @Named( value = "archivaTaskScheduler#repository" )
182     private RepositoryArchivaTaskScheduler scheduler;
183
184     @Inject
185     @Named( value = "fileLockManager#default" )
186     private FileLockManager fileLockManager;
187
188     private ApplicationContext applicationContext;
189
190     @Inject
191     public ArchivaDavResourceFactory( ApplicationContext applicationContext, PlexusSisuBridge plexusSisuBridge,
192                                       ArchivaConfiguration archivaConfiguration )
193         throws PlexusSisuBridgeException
194     {
195         this.archivaConfiguration = archivaConfiguration;
196         this.applicationContext = applicationContext;
197         this.checksum = plexusSisuBridge.lookup( ChecksumFile.class );
198
199         this.digestMd5 = plexusSisuBridge.lookup( Digester.class, "md5" );
200         this.digestSha1 = plexusSisuBridge.lookup( Digester.class, "sha1" );
201
202         // TODO remove this hard dependency on maven !!
203         repositoryRequest = new RepositoryRequest( new LegacyPathParser( archivaConfiguration ) );
204     }
205
206     @PostConstruct
207     public void initialize()
208     {
209         // no op
210     }
211
212     @Override
213     public DavResource createResource( final DavResourceLocator locator, final DavServletRequest request,
214                                        final DavServletResponse response )
215         throws DavException
216     {
217         ArchivaDavResourceLocator archivaLocator = checkLocatorIsInstanceOfRepositoryLocator( locator );
218
219         RepositoryGroupConfiguration repoGroupConfig =
220             archivaConfiguration.getConfiguration().getRepositoryGroupsAsMap().get( archivaLocator.getRepositoryId() );
221
222         String activePrincipal = getActivePrincipal( request );
223
224         List<String> resourcesInAbsolutePath = new ArrayList<>();
225
226         boolean readMethod = WebdavMethodUtil.isReadMethod( request.getMethod() );
227         DavResource resource;
228         if ( repoGroupConfig != null )
229         {
230             if ( !readMethod )
231             {
232                 throw new DavException( HttpServletResponse.SC_METHOD_NOT_ALLOWED,
233                                         "Write method not allowed for repository groups." );
234             }
235
236             log.debug( "Repository group '{}' accessed by '{}", repoGroupConfig.getId(), activePrincipal );
237
238             // handle browse requests for virtual repos
239             if ( getLogicalResource( archivaLocator, null, true ).endsWith( "/" ) )
240             {
241                 try
242                 {
243                     DavResource davResource =
244                         getResourceFromGroup( request, repoGroupConfig.getRepositories(), archivaLocator,
245                                               repoGroupConfig );
246
247                     setHeaders( response, locator, davResource );
248
249                     return davResource;
250
251                 }
252                 catch ( RepositoryAdminException e )
253                 {
254                     throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
255                 }
256             }
257             else
258             {
259                 // make a copy to avoid potential concurrent modifications (eg. by configuration)
260                 // TODO: ultimately, locking might be more efficient than copying in this fashion since updates are
261                 //  infrequent
262                 List<String> repositories = new ArrayList<>( repoGroupConfig.getRepositories() );
263                 resource = processRepositoryGroup( request, archivaLocator, repositories, activePrincipal,
264                                                    resourcesInAbsolutePath, repoGroupConfig );
265             }
266         }
267         else
268         {
269
270             try
271             {
272                 RemoteRepository remoteRepository =
273                     remoteRepositoryAdmin.getRemoteRepository( archivaLocator.getRepositoryId() );
274
275                 if ( remoteRepository != null )
276                 {
277                     String logicalResource = getLogicalResource( archivaLocator, null, false );
278                     IndexingContext indexingContext = remoteRepositoryAdmin.createIndexContext( remoteRepository );
279                     File resourceFile = StringUtils.equals( logicalResource, "/" )
280                         ? new File( indexingContext.getIndexDirectoryFile().getParent() )
281                         : new File( indexingContext.getIndexDirectoryFile().getParent(), logicalResource );
282                     resource = new ArchivaDavResource( resourceFile.getAbsolutePath(), //
283                                                        locator.getResourcePath(), //
284                                                        null, //
285                                                        request.getRemoteAddr(), //
286                                                        activePrincipal, //
287                                                        request.getDavSession(), //
288                                                        archivaLocator, //
289                                                        this, //
290                                                        mimeTypes, //
291                                                        auditListeners, //
292                                                        scheduler, //
293                                                        fileLockManager );
294                     setHeaders( response, locator, resource );
295                     return resource;
296                 }
297             }
298             catch ( RepositoryAdminException e )
299             {
300                 log.debug( "RepositoryException remote repository with d'{}' not found, msg: {}",
301                            archivaLocator.getRepositoryId(), e.getMessage() );
302             }
303
304             ManagedRepositoryContent managedRepositoryContent = null;
305
306             try
307             {
308                 managedRepositoryContent =
309                     repositoryFactory.getManagedRepositoryContent( archivaLocator.getRepositoryId() );
310             }
311             catch ( RepositoryNotFoundException e )
312             {
313                 throw new DavException( HttpServletResponse.SC_NOT_FOUND,
314                                         "Invalid repository: " + archivaLocator.getRepositoryId() );
315             }
316             catch ( RepositoryException e )
317             {
318                 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
319             }
320
321             log.debug( "Managed repository '{}' accessed by '{}'", managedRepositoryContent.getId(), activePrincipal );
322
323             try
324             {
325                 resource = processRepository( request, archivaLocator, activePrincipal, managedRepositoryContent,
326                                               managedRepositoryAdmin.getManagedRepository(
327                                                   archivaLocator.getRepositoryId() ) );
328
329                 String logicalResource = getLogicalResource( archivaLocator, null, false );
330                 resourcesInAbsolutePath.add(
331                     new File( managedRepositoryContent.getRepoRoot(), logicalResource ).getAbsolutePath() );
332
333             }
334             catch ( RepositoryAdminException e )
335             {
336                 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
337             }
338         }
339
340         String requestedResource = request.getRequestURI();
341
342         // MRM-872 : merge all available metadata
343         // merge metadata only when requested via the repo group
344         if ( ( repositoryRequest.isMetadata( requestedResource ) || repositoryRequest.isMetadataSupportFile(
345             requestedResource ) ) && repoGroupConfig != null )
346         {
347             // this should only be at the project level not version level!
348             if ( isProjectReference( requestedResource ) )
349             {
350
351                 ArchivaDavResource res = (ArchivaDavResource) resource;
352                 String filePath =
353                     StringUtils.substringBeforeLast( res.getLocalResource().getAbsolutePath().replace( '\\', '/' ),
354                                                      "/" );
355                 filePath = filePath + "/maven-metadata-" + repoGroupConfig.getId() + ".xml";
356
357                 // for MRM-872 handle checksums of the merged metadata files
358                 if ( repositoryRequest.isSupportFile( requestedResource ) )
359                 {
360                     File metadataChecksum =
361                         new File( filePath + "." + StringUtils.substringAfterLast( requestedResource, "." ) );
362
363                     if ( metadataChecksum.exists() )
364                     {
365                         LogicalResource logicalResource =
366                             new LogicalResource( getLogicalResource( archivaLocator, null, false ) );
367
368                         resource =
369                             new ArchivaDavResource( metadataChecksum.getAbsolutePath(), logicalResource.getPath(), null,
370                                                     request.getRemoteAddr(), activePrincipal, request.getDavSession(),
371                                                     archivaLocator, this, mimeTypes, auditListeners, scheduler,
372                                                     fileLockManager );
373                     }
374                 }
375                 else
376                 {
377                     if ( resourcesInAbsolutePath != null && resourcesInAbsolutePath.size() > 1 )
378                     {
379                         // merge the metadata of all repos under group
380                         ArchivaRepositoryMetadata mergedMetadata = new ArchivaRepositoryMetadata();
381                         for ( String resourceAbsPath : resourcesInAbsolutePath )
382                         {
383                             try
384                             {
385                                 File metadataFile = new File( resourceAbsPath );
386                                 ArchivaRepositoryMetadata repoMetadata = MavenMetadataReader.read( metadataFile );
387                                 mergedMetadata = RepositoryMetadataMerge.merge( mergedMetadata, repoMetadata );
388                             }
389                             catch ( XMLException e )
390                             {
391                                 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
392                                                         "Error occurred while reading metadata file." );
393                             }
394                             catch ( RepositoryMetadataException r )
395                             {
396                                 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
397                                                         "Error occurred while merging metadata file." );
398                             }
399                         }
400
401                         try
402                         {
403                             File resourceFile = writeMergedMetadataToFile( mergedMetadata, filePath );
404
405                             LogicalResource logicalResource =
406                                 new LogicalResource( getLogicalResource( archivaLocator, null, false ) );
407
408                             resource =
409                                 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), null,
410                                                         request.getRemoteAddr(), activePrincipal,
411                                                         request.getDavSession(), archivaLocator, this, mimeTypes,
412                                                         auditListeners, scheduler, fileLockManager );
413                         }
414                         catch ( RepositoryMetadataException r )
415                         {
416                             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
417                                                     "Error occurred while writing metadata file." );
418                         }
419                         catch ( IOException ie )
420                         {
421                             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
422                                                     "Error occurred while generating checksum files." );
423                         }
424                         catch ( DigesterException de )
425                         {
426                             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
427                                                     "Error occurred while generating checksum files."
428                                                         + de.getMessage() );
429                         }
430                     }
431                 }
432             }
433         }
434
435         setHeaders( response, locator, resource );
436
437         // compatibility with MRM-440 to ensure browsing the repository works ok
438         if ( resource.isCollection() && !request.getRequestURI().endsWith( "/" ) )
439         {
440             throw new BrowserRedirectException( resource.getHref() );
441         }
442         resource.addLockManager( lockManager );
443         return resource;
444     }
445
446     private DavResource processRepositoryGroup( final DavServletRequest request,
447                                                 ArchivaDavResourceLocator archivaLocator, List<String> repositories,
448                                                 String activePrincipal, List<String> resourcesInAbsolutePath,
449                                                 RepositoryGroupConfiguration repoGroupConfig )
450         throws DavException
451     {
452         DavResource resource = null;
453         List<DavException> storedExceptions = new ArrayList<>();
454
455         String pathInfo = StringUtils.removeEnd( request.getPathInfo(), "/" );
456
457         String rootPath = StringUtils.substringBeforeLast( pathInfo, "/" );
458
459         if ( StringUtils.endsWith( rootPath, repoGroupConfig.getMergedIndexPath() ) )
460         {
461             // we are in the case of index file request
462             String requestedFileName = StringUtils.substringAfterLast( pathInfo, "/" );
463             File temporaryIndexDirectory =
464                 buildMergedIndexDirectory( repositories, activePrincipal, request, repoGroupConfig );
465
466             File resourceFile = new File( temporaryIndexDirectory, requestedFileName );
467             resource = new ArchivaDavResource( resourceFile.getAbsolutePath(), requestedFileName, null,
468                                                request.getRemoteAddr(), activePrincipal, request.getDavSession(),
469                                                archivaLocator, this, mimeTypes, auditListeners, scheduler,
470                                                fileLockManager );
471
472         }
473         else
474         {
475             for ( String repositoryId : repositories )
476             {
477                 ManagedRepositoryContent managedRepositoryContent;
478                 try
479                 {
480                     managedRepositoryContent = repositoryFactory.getManagedRepositoryContent( repositoryId );
481                 }
482                 catch ( RepositoryNotFoundException e )
483                 {
484                     throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
485                 }
486                 catch ( RepositoryException e )
487                 {
488                     throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
489                 }
490
491                 try
492                 {
493                     ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repositoryId );
494                     DavResource updatedResource =
495                         processRepository( request, archivaLocator, activePrincipal, managedRepositoryContent,
496                                            managedRepository );
497                     if ( resource == null )
498                     {
499                         resource = updatedResource;
500                     }
501
502                     String logicalResource = getLogicalResource( archivaLocator, null, false );
503                     if ( logicalResource.endsWith( "/" ) )
504                     {
505                         logicalResource = logicalResource.substring( 1 );
506                     }
507                     resourcesInAbsolutePath.add(
508                         new File( managedRepositoryContent.getRepoRoot(), logicalResource ).getAbsolutePath() );
509                 }
510                 catch ( DavException e )
511                 {
512                     storedExceptions.add( e );
513                 }
514                 catch ( RepositoryAdminException e )
515                 {
516                     storedExceptions.add( new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e ) );
517                 }
518             }
519         }
520         if ( resource == null )
521         {
522             if ( !storedExceptions.isEmpty() )
523             {
524                 // MRM-1232
525                 for ( DavException e : storedExceptions )
526                 {
527                     if ( 401 == e.getErrorCode() )
528                     {
529                         throw e;
530                     }
531                 }
532
533                 throw new DavException( HttpServletResponse.SC_NOT_FOUND );
534             }
535             else
536             {
537                 throw new DavException( HttpServletResponse.SC_NOT_FOUND );
538             }
539         }
540         return resource;
541     }
542
543     private String getLogicalResource( ArchivaDavResourceLocator archivaLocator, ManagedRepository managedRepository,
544                                        boolean useOrigResourcePath )
545     {
546         // FIXME remove this hack
547         // but currently managedRepository can be null in case of group
548         String layout = managedRepository == null ? new ManagedRepository().getLayout() : managedRepository.getLayout();
549         RepositoryStorage repositoryStorage =
550             this.applicationContext.getBean( "repositoryStorage#" + layout, RepositoryStorage.class );
551         String path = repositoryStorage.getFilePath(
552             useOrigResourcePath ? archivaLocator.getOrigResourcePath() : archivaLocator.getResourcePath(),
553             managedRepository );
554         log.debug( "found path {} for resourcePath: '{}' with managedRepo '{}' and layout '{}'", path,
555                    archivaLocator.getResourcePath(), managedRepository == null ? "null" : managedRepository.getId(),
556                    layout );
557         return path;
558     }
559
560     private String evaluatePathWithVersion( ArchivaDavResourceLocator archivaLocator,
561                                             ManagedRepositoryContent managedRepositoryContent, String contextPath )
562         throws DavException
563     {
564         String layout = managedRepositoryContent.getRepository() == null
565             ? new ManagedRepository().getLayout()
566             : managedRepositoryContent.getRepository().getLayout();
567         RepositoryStorage repositoryStorage =
568             this.applicationContext.getBean( "repositoryStorage#" + layout, RepositoryStorage.class );
569         try
570         {
571             return repositoryStorage.getFilePathWithVersion( archivaLocator.getResourcePath(),
572                                                              managedRepositoryContent );
573         }
574         catch ( RelocationException e )
575         {
576             String path = e.getPath();
577             log.debug( "Relocation to {}", path );
578
579             throw new BrowserRedirectException( contextPath + ( StringUtils.startsWith( path, "/" ) ? "" : "/" ) + path,
580                                                 e.getRelocationType() );
581         }
582         catch ( XMLException e )
583         {
584             log.error( e.getMessage(), e );
585             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
586         }
587     }
588
589     private DavResource processRepository( final DavServletRequest request, ArchivaDavResourceLocator archivaLocator,
590                                            String activePrincipal, ManagedRepositoryContent managedRepositoryContent,
591                                            ManagedRepository managedRepository )
592         throws DavException
593     {
594         DavResource resource = null;
595         if ( isAuthorized( request, managedRepositoryContent.getId() ) )
596         {
597             boolean readMethod = WebdavMethodUtil.isReadMethod( request.getMethod() );
598             // Maven Centric part ask evaluation if -SNAPSHOT
599             // MRM-1846 test if read method to prevent issue with maven 2.2.1 and uniqueVersion false
600
601             String path = readMethod
602                 ? evaluatePathWithVersion( archivaLocator, managedRepositoryContent, request.getContextPath() )
603                 : getLogicalResource( archivaLocator, managedRepository, false );
604             if ( path.startsWith( "/" ) )
605             {
606                 path = path.substring( 1 );
607             }
608             LogicalResource logicalResource = new LogicalResource( path );
609             File resourceFile = new File( managedRepositoryContent.getRepoRoot(), path );
610             resource =
611                 new ArchivaDavResource( resourceFile.getAbsolutePath(), path, managedRepositoryContent.getRepository(),
612                                         request.getRemoteAddr(), activePrincipal, request.getDavSession(),
613                                         archivaLocator, this, mimeTypes, auditListeners, scheduler, fileLockManager );
614
615             if ( WebdavMethodUtil.isReadMethod( request.getMethod() ) )
616             {
617                 if ( archivaLocator.getHref( false ).endsWith( "/" ) && !resourceFile.isDirectory() )
618                 {
619                     // force a resource not found
620                     throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource does not exist" );
621                 }
622                 else
623                 {
624                     if ( !resource.isCollection() )
625                     {
626                         boolean previouslyExisted = resourceFile.exists();
627
628                         // Attempt to fetch the resource from any defined proxy.
629                         boolean fromProxy =
630                             fetchContentFromProxies( managedRepositoryContent, request, logicalResource );
631
632                         // At this point the incoming request can either be in default or
633                         // legacy layout format.
634                         try
635                         {
636                             // Perform an adjustment of the resource to the managed
637                             // repository expected path.
638                             String localResourcePath =
639                                 repositoryRequest.toNativePath( logicalResource.getPath(), managedRepositoryContent );
640                             resourceFile = new File( managedRepositoryContent.getRepoRoot(), localResourcePath );
641                             resource =
642                                 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(),
643                                                         managedRepositoryContent.getRepository(),
644                                                         request.getRemoteAddr(), activePrincipal,
645                                                         request.getDavSession(), archivaLocator, this, mimeTypes,
646                                                         auditListeners, scheduler, fileLockManager );
647                         }
648                         catch ( LayoutException e )
649                         {
650                             if ( !resourceFile.exists() )
651                             {
652                                 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
653                             }
654                         }
655
656                         if ( fromProxy )
657                         {
658                             String event = ( previouslyExisted ? AuditEvent.MODIFY_FILE : AuditEvent.CREATE_FILE )
659                                 + PROXIED_SUFFIX;
660
661                             log.debug( "Proxied artifact '{}' in repository '{}' (current user '{}')",
662                                        resourceFile.getName(), managedRepositoryContent.getId(), activePrincipal );
663
664                             triggerAuditEvent( request.getRemoteAddr(), archivaLocator.getRepositoryId(),
665                                                logicalResource.getPath(), event, activePrincipal );
666                         }
667
668                         if ( !resourceFile.exists() )
669                         {
670                             throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource does not exist" );
671                         }
672                     }
673                 }
674             }
675
676             if ( request.getMethod().equals( HTTP_PUT_METHOD ) )
677             {
678                 String resourcePath = logicalResource.getPath();
679
680                 // check if target repo is enabled for releases
681                 // we suppose that release-artifacts can be deployed only to repos enabled for releases
682                 if ( managedRepositoryContent.getRepository().isReleases() && !repositoryRequest.isMetadata(
683                     resourcePath ) && !repositoryRequest.isSupportFile( resourcePath ) )
684                 {
685                     ArtifactReference artifact = null;
686                     try
687                     {
688                         artifact = managedRepositoryContent.toArtifactReference( resourcePath );
689
690                         if ( !VersionUtil.isSnapshot( artifact.getVersion() ) )
691                         {
692                             // check if artifact already exists and if artifact re-deployment to the repository is allowed
693                             if ( managedRepositoryContent.hasContent( artifact )
694                                 && managedRepositoryContent.getRepository().isBlockRedeployments() )
695                             {
696                                 log.warn( "Overwriting released artifacts in repository '{}' is not allowed.",
697                                           managedRepositoryContent.getId() );
698                                 throw new DavException( HttpServletResponse.SC_CONFLICT,
699                                                         "Overwriting released artifacts is not allowed." );
700                             }
701                         }
702                     }
703                     catch ( LayoutException e )
704                     {
705                         log.warn( "Artifact path '{}' is invalid.", resourcePath );
706                     }
707                 }
708
709                 /*
710                  * Create parent directories that don't exist when writing a file This actually makes this
711                  * implementation not compliant to the WebDAV RFC - but we have enough knowledge about how the
712                  * collection is being used to do this reasonably and some versions of Maven's WebDAV don't correctly
713                  * create the collections themselves.
714                  */
715
716                 File rootDirectory = new File( managedRepositoryContent.getRepoRoot() );
717                 File destDir = new File( rootDirectory, logicalResource.getPath() ).getParentFile();
718
719                 if ( !destDir.exists() )
720                 {
721                     destDir.mkdirs();
722                     String relPath = PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
723
724                     log.debug( "Creating destination directory '{}' (current user '{}')", destDir.getName(),
725                                activePrincipal );
726
727                     triggerAuditEvent( request.getRemoteAddr(), managedRepositoryContent.getId(), relPath,
728                                        AuditEvent.CREATE_DIR, activePrincipal );
729                 }
730             }
731         }
732         return resource;
733     }
734
735     @Override
736     public DavResource createResource( final DavResourceLocator locator, final DavSession davSession )
737         throws DavException
738     {
739         ArchivaDavResourceLocator archivaLocator = checkLocatorIsInstanceOfRepositoryLocator( locator );
740
741         ManagedRepositoryContent managedRepositoryContent;
742         try
743         {
744             managedRepositoryContent =
745                 repositoryFactory.getManagedRepositoryContent( archivaLocator.getRepositoryId() );
746         }
747         catch ( RepositoryNotFoundException e )
748         {
749             throw new DavException( HttpServletResponse.SC_NOT_FOUND,
750                                     "Invalid repository: " + archivaLocator.getRepositoryId() );
751         }
752         catch ( RepositoryException e )
753         {
754             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
755         }
756
757         DavResource resource = null;
758         try
759         {
760             String logicalResource = getLogicalResource( archivaLocator, managedRepositoryAdmin.getManagedRepository(
761                 archivaLocator.getRepositoryId() ), false );
762             if ( logicalResource.startsWith( "/" ) )
763             {
764                 logicalResource = logicalResource.substring( 1 );
765             }
766             File resourceFile = new File( managedRepositoryContent.getRepoRoot(), logicalResource );
767             resource = new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource,
768                                                managedRepositoryContent.getRepository(), davSession, archivaLocator,
769                                                this, mimeTypes, auditListeners, scheduler, fileLockManager );
770
771             resource.addLockManager( lockManager );
772         }
773         catch ( RepositoryAdminException e )
774         {
775             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
776         }
777         return resource;
778     }
779
780     private boolean fetchContentFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request,
781                                              LogicalResource resource )
782         throws DavException
783     {
784         String path = resource.getPath();
785         if ( repositoryRequest.isSupportFile( path ) )
786         {
787             File proxiedFile = connectors.fetchFromProxies( managedRepository, path );
788
789             return ( proxiedFile != null );
790         }
791
792         // Is it a Metadata resource?
793         if ( repositoryRequest.isDefault( path ) && repositoryRequest.isMetadata( path ) )
794         {
795             return connectors.fetchMetatadaFromProxies( managedRepository, path ) != null;
796         }
797
798         // Is it an Archetype Catalog?
799         if ( repositoryRequest.isArchetypeCatalog( path ) )
800         {
801             // FIXME we must implement a merge of remote archetype catalog from remote servers.
802             File proxiedFile = connectors.fetchFromProxies( managedRepository, path );
803
804             return ( proxiedFile != null );
805         }
806
807         // Not any of the above? Then it's gotta be an artifact reference.
808         try
809         {
810             // Get the artifact reference in a layout neutral way.
811             ArtifactReference artifact = repositoryRequest.toArtifactReference( path );
812
813             if ( artifact != null )
814             {
815                 String repositoryLayout = managedRepository.getRepository().getLayout();
816
817                 RepositoryStorage repositoryStorage =
818                     this.applicationContext.getBean( "repositoryStorage#" + repositoryLayout, RepositoryStorage.class );
819                 repositoryStorage.applyServerSideRelocation( managedRepository, artifact );
820
821                 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
822
823                 resource.setPath( managedRepository.toPath( artifact ) );
824
825                 log.debug( "Proxied artifact '{}:{}:{}'", artifact.getGroupId(), artifact.getArtifactId(),
826                            artifact.getVersion() );
827
828                 return ( proxiedFile != null );
829             }
830         }
831         catch ( LayoutException e )
832         {
833             /* eat it */
834         }
835         catch ( ProxyDownloadException e )
836         {
837             log.error( e.getMessage(), e );
838             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
839                                     "Unable to fetch artifact resource." );
840         }
841         return false;
842     }
843
844     // TODO: remove?
845
846     private void triggerAuditEvent( String remoteIP, String repositoryId, String resource, String action,
847                                     String principal )
848     {
849         AuditEvent event = new AuditEvent( repositoryId, principal, resource, action );
850         event.setRemoteIP( remoteIP );
851
852         for ( AuditListener listener : auditListeners )
853         {
854             listener.auditEvent( event );
855         }
856     }
857
858     @Override
859     public void addAuditListener( AuditListener listener )
860     {
861         this.auditListeners.add( listener );
862     }
863
864     @Override
865     public void clearAuditListeners()
866     {
867         this.auditListeners.clear();
868     }
869
870     @Override
871     public void removeAuditListener( AuditListener listener )
872     {
873         this.auditListeners.remove( listener );
874     }
875
876     private void setHeaders( DavServletResponse response, DavResourceLocator locator, DavResource resource )
877     {
878         // [MRM-503] - Metadata file need Pragma:no-cache response
879         // header.
880         if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) || ( resource instanceof ArchivaDavResource
881             && ( ArchivaDavResource.class.cast( resource ).getLocalResource().isDirectory() ) ) )
882         {
883             response.setHeader( "Pragma", "no-cache" );
884             response.setHeader( "Cache-Control", "no-cache" );
885             response.setDateHeader( "Last-Modified", new Date().getTime() );
886         }
887         // if the resource is a directory don't cache it as new groupId deployed will be available
888         // without need of refreshing browser
889         else if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) || (
890             resource instanceof ArchivaVirtualDavResource && ( new File(
891                 ArchivaVirtualDavResource.class.cast( resource ).getLogicalResource() ).isDirectory() ) ) )
892         {
893             response.setHeader( "Pragma", "no-cache" );
894             response.setHeader( "Cache-Control", "no-cache" );
895             response.setDateHeader( "Last-Modified", new Date().getTime() );
896         }
897         else
898         {
899             // We need to specify this so connecting wagons can work correctly
900             response.setDateHeader( "Last-Modified", resource.getModificationTime() );
901         }
902         // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots)
903     }
904
905     private ArchivaDavResourceLocator checkLocatorIsInstanceOfRepositoryLocator( DavResourceLocator locator )
906         throws DavException
907     {
908         if ( !( locator instanceof ArchivaDavResourceLocator ) )
909         {
910             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
911                                     "Locator does not implement RepositoryLocator" );
912         }
913
914         // Hidden paths
915         if ( locator.getResourcePath().startsWith( ArchivaDavResource.HIDDEN_PATH_PREFIX ) )
916         {
917             throw new DavException( HttpServletResponse.SC_NOT_FOUND );
918         }
919
920         ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator) locator;
921
922         // MRM-419 - Windows Webdav support. Should not 404 if there is no content.
923         if ( StringUtils.isEmpty( archivaLocator.getRepositoryId() ) )
924         {
925             throw new DavException( HttpServletResponse.SC_NO_CONTENT );
926         }
927         return archivaLocator;
928     }
929
930     private static class LogicalResource
931     {
932         private String path;
933
934         public LogicalResource( String path )
935         {
936             this.path = path;
937         }
938
939         public String getPath()
940         {
941             return path;
942         }
943
944         public void setPath( String path )
945         {
946             this.path = path;
947         }
948     }
949
950     protected boolean isAuthorized( DavServletRequest request, String repositoryId )
951         throws DavException
952     {
953         try
954         {
955             AuthenticationResult result = httpAuth.getAuthenticationResult( request, null );
956             SecuritySession securitySession = httpAuth.getSecuritySession( request.getSession( true ) );
957
958             return servletAuth.isAuthenticated( request, result ) //
959                 && servletAuth.isAuthorized( request, securitySession, repositoryId, //
960                                              WebdavMethodUtil.getMethodPermission( request.getMethod() ) );
961         }
962         catch ( AuthenticationException e )
963         {
964             // safety check for MRM-911
965             String guest = UserManager.GUEST_USERNAME;
966             try
967             {
968                 if ( servletAuth.isAuthorized( guest,
969                                                ( (ArchivaDavResourceLocator) request.getRequestLocator() ).getRepositoryId(),
970                                                WebdavMethodUtil.getMethodPermission( request.getMethod() ) ) )
971                 {
972                     return true;
973                 }
974             }
975             catch ( UnauthorizedException ae )
976             {
977                 throw new UnauthorizedDavException( repositoryId,
978                                                     "You are not authenticated and authorized to access any repository." );
979             }
980
981             throw new UnauthorizedDavException( repositoryId, "You are not authenticated" );
982         }
983         catch ( MustChangePasswordException e )
984         {
985             throw new UnauthorizedDavException( repositoryId, "You must change your password." );
986         }
987         catch ( AccountLockedException e )
988         {
989             throw new UnauthorizedDavException( repositoryId, "User account is locked." );
990         }
991         catch ( AuthorizationException e )
992         {
993             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
994                                     "Fatal Authorization Subsystem Error." );
995         }
996         catch ( UnauthorizedException e )
997         {
998             throw new UnauthorizedDavException( repositoryId, e.getMessage() );
999         }
1000     }
1001
1002     private DavResource getResourceFromGroup( DavServletRequest request, List<String> repositories,
1003                                               ArchivaDavResourceLocator locator,
1004                                               RepositoryGroupConfiguration repositoryGroupConfiguration )
1005         throws DavException, RepositoryAdminException
1006     {
1007         if ( repositoryGroupConfiguration.getRepositories() == null
1008             || repositoryGroupConfiguration.getRepositories().isEmpty() )
1009         {
1010             File file =
1011                 new File( System.getProperty( "appserver.base" ), "groups/" + repositoryGroupConfiguration.getId() );
1012
1013             return new ArchivaDavResource( file.getPath(), "groups/" + repositoryGroupConfiguration.getId(), null,
1014                                            request.getDavSession(), locator, this, mimeTypes, auditListeners, scheduler,
1015                                            fileLockManager );
1016         }
1017         List<File> mergedRepositoryContents = new ArrayList<>();
1018         // multiple repo types so we guess they are all the same type
1019         // so use the first one
1020         // FIXME add a method with group in the repository storage
1021         String firstRepoId = repositoryGroupConfiguration.getRepositories().get( 0 );
1022
1023         String path = getLogicalResource( locator, managedRepositoryAdmin.getManagedRepository( firstRepoId ), false );
1024         if ( path.startsWith( "/" ) )
1025         {
1026             path = path.substring( 1 );
1027         }
1028         LogicalResource logicalResource = new LogicalResource( path );
1029
1030         // flow:
1031         // if the current user logged in has permission to any of the repositories, allow user to
1032         // browse the repo group but displaying only the repositories which the user has permission to access.
1033         // otherwise, prompt for authentication.
1034
1035         String activePrincipal = getActivePrincipal( request );
1036
1037         boolean allow = isAllowedToContinue( request, repositories, activePrincipal );
1038
1039         // remove last /
1040         String pathInfo = StringUtils.removeEnd( request.getPathInfo(), "/" );
1041
1042         if ( allow )
1043         {
1044
1045             if ( StringUtils.endsWith( pathInfo, repositoryGroupConfiguration.getMergedIndexPath() ) )
1046             {
1047                 File mergedRepoDir =
1048                     buildMergedIndexDirectory( repositories, activePrincipal, request, repositoryGroupConfiguration );
1049                 mergedRepositoryContents.add( mergedRepoDir );
1050             }
1051             else
1052             {
1053                 if ( StringUtils.equalsIgnoreCase( pathInfo, "/" + repositoryGroupConfiguration.getId() ) )
1054                 {
1055                     File tmpDirectory = new File( SystemUtils.getJavaIoTmpDir(),
1056                                                   repositoryGroupConfiguration.getId() + "/"
1057                                                       + repositoryGroupConfiguration.getMergedIndexPath() );
1058                     if ( !tmpDirectory.exists() )
1059                     {
1060                         synchronized ( tmpDirectory.getAbsolutePath() )
1061                         {
1062                             if ( !tmpDirectory.exists() )
1063                             {
1064                                 tmpDirectory.mkdirs();
1065                             }
1066                         }
1067                     }
1068                     mergedRepositoryContents.add( tmpDirectory.getParentFile() );
1069                 }
1070                 for ( String repository : repositories )
1071                 {
1072                     ManagedRepositoryContent managedRepository = null;
1073
1074                     try
1075                     {
1076                         managedRepository = repositoryFactory.getManagedRepositoryContent( repository );
1077                     }
1078                     catch ( RepositoryNotFoundException e )
1079                     {
1080                         throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1081                                                 "Invalid managed repository <" + repository + ">: " + e.getMessage() );
1082                     }
1083                     catch ( RepositoryException e )
1084                     {
1085                         throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1086                                                 "Invalid managed repository <" + repository + ">: " + e.getMessage() );
1087                     }
1088
1089                     File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
1090                     if ( resourceFile.exists() )
1091                     {
1092                         // in case of group displaying index directory doesn't have sense !!
1093                         String repoIndexDirectory = managedRepository.getRepository().getIndexDirectory();
1094                         if ( StringUtils.isNotEmpty( repoIndexDirectory ) )
1095                         {
1096                             if ( !new File( repoIndexDirectory ).isAbsolute() )
1097                             {
1098                                 repoIndexDirectory = new File( managedRepository.getRepository().getLocation(),
1099                                                                StringUtils.isEmpty( repoIndexDirectory )
1100                                                                    ? ".indexer"
1101                                                                    : repoIndexDirectory ).getAbsolutePath();
1102                             }
1103                         }
1104                         if ( StringUtils.isEmpty( repoIndexDirectory ) )
1105                         {
1106                             repoIndexDirectory = new File( managedRepository.getRepository().getLocation(),
1107                                                            ".indexer" ).getAbsolutePath();
1108                         }
1109
1110                         if ( !StringUtils.equals( FilenameUtils.normalize( repoIndexDirectory ),
1111                                                   FilenameUtils.normalize( resourceFile.getAbsolutePath() ) ) )
1112                         {
1113                             // for prompted authentication
1114                             if ( httpAuth.getSecuritySession( request.getSession( true ) ) != null )
1115                             {
1116                                 try
1117                                 {
1118                                     if ( isAuthorized( request, repository ) )
1119                                     {
1120                                         mergedRepositoryContents.add( resourceFile );
1121                                         log.debug( "Repository '{}' accessed by '{}'", repository, activePrincipal );
1122                                     }
1123                                 }
1124                                 catch ( DavException e )
1125                                 {
1126                                     // TODO: review exception handling
1127
1128                                     log.debug( "Skipping repository '{}' for user '{}': {}", managedRepository,
1129                                                activePrincipal, e.getMessage() );
1130
1131                                 }
1132
1133                             }
1134                             else
1135                             {
1136                                 // for the current user logged in
1137                                 try
1138                                 {
1139                                     if ( servletAuth.isAuthorized( activePrincipal, repository,
1140                                                                    WebdavMethodUtil.getMethodPermission(
1141                                                                        request.getMethod() ) ) )
1142                                     {
1143                                         mergedRepositoryContents.add( resourceFile );
1144                                         log.debug( "Repository '{}' accessed by '{}'", repository, activePrincipal );
1145                                     }
1146                                 }
1147                                 catch ( UnauthorizedException e )
1148                                 {
1149                                     // TODO: review exception handling
1150
1151                                     log.debug( "Skipping repository '{}' for user '{}': {}", managedRepository,
1152                                                activePrincipal, e.getMessage() );
1153
1154                                 }
1155                             }
1156                         }
1157                     }
1158                 }
1159             }
1160         }
1161         else
1162         {
1163             throw new UnauthorizedDavException( locator.getRepositoryId(), "User not authorized." );
1164         }
1165
1166         ArchivaVirtualDavResource resource =
1167             new ArchivaVirtualDavResource( mergedRepositoryContents, logicalResource.getPath(), mimeTypes, locator,
1168                                            this );
1169
1170         // compatibility with MRM-440 to ensure browsing the repository group works ok
1171         if ( resource.isCollection() && !request.getRequestURI().endsWith( "/" ) )
1172         {
1173             throw new BrowserRedirectException( resource.getHref() );
1174         }
1175
1176         return resource;
1177     }
1178
1179     protected String getActivePrincipal( DavServletRequest request )
1180     {
1181         User sessionUser = httpAuth.getSessionUser( request.getSession() );
1182         return sessionUser != null ? sessionUser.getUsername() : UserManager.GUEST_USERNAME;
1183     }
1184
1185     /**
1186      * Check if the current user is authorized to access any of the repos
1187      *
1188      * @param request
1189      * @param repositories
1190      * @param activePrincipal
1191      * @return
1192      */
1193     private boolean isAllowedToContinue( DavServletRequest request, List<String> repositories, String activePrincipal )
1194     {
1195         // when no repositories configured it's impossible to browse nothing !
1196         // at least make possible to see nothing :-)
1197         if ( repositories == null || repositories.isEmpty() )
1198         {
1199             return true;
1200         }
1201
1202         boolean allow = false;
1203
1204         // if securitySession != null, it means that the user was prompted for authentication
1205         if ( httpAuth.getSecuritySession( request.getSession() ) != null )
1206         {
1207             for ( String repository : repositories )
1208             {
1209                 try
1210                 {
1211                     if ( isAuthorized( request, repository ) )
1212                     {
1213                         allow = true;
1214                         break;
1215                     }
1216                 }
1217                 catch ( DavException e )
1218                 {
1219                     continue;
1220                 }
1221             }
1222         }
1223         else
1224         {
1225             for ( String repository : repositories )
1226             {
1227                 try
1228                 {
1229                     if ( servletAuth.isAuthorized( activePrincipal, repository,
1230                                                    WebdavMethodUtil.getMethodPermission( request.getMethod() ) ) )
1231                     {
1232                         allow = true;
1233                         break;
1234                     }
1235                 }
1236                 catch ( UnauthorizedException e )
1237                 {
1238                     continue;
1239                 }
1240             }
1241         }
1242
1243         return allow;
1244     }
1245
1246     private File writeMergedMetadataToFile( ArchivaRepositoryMetadata mergedMetadata, String outputFilename )
1247         throws RepositoryMetadataException, DigesterException, IOException
1248     {
1249         File outputFile = new File( outputFilename );
1250         if ( outputFile.exists() )
1251         {
1252             FileUtils.deleteQuietly( outputFile );
1253         }
1254
1255         outputFile.getParentFile().mkdirs();
1256         RepositoryMetadataWriter.write( mergedMetadata, outputFile );
1257
1258         createChecksumFile( outputFilename, digestSha1 );
1259         createChecksumFile( outputFilename, digestMd5 );
1260
1261         return outputFile;
1262     }
1263
1264     private void createChecksumFile( String path, Digester digester )
1265         throws DigesterException, IOException
1266     {
1267         File checksumFile = new File( path + digester.getFilenameExtension() );
1268         if ( !checksumFile.exists() )
1269         {
1270             FileUtils.deleteQuietly( checksumFile );
1271             checksum.createChecksum( new File( path ), digester );
1272         }
1273         else if ( !checksumFile.isFile() )
1274         {
1275             log.error( "Checksum file is not a file." );
1276         }
1277     }
1278
1279     private boolean isProjectReference( String requestedResource )
1280     {
1281         try
1282         {
1283             metadataTools.toVersionedReference( requestedResource );
1284             return false;
1285         }
1286         catch ( RepositoryMetadataException re )
1287         {
1288             return true;
1289         }
1290     }
1291
1292     protected File buildMergedIndexDirectory( List<String> repositories, String activePrincipal,
1293                                               DavServletRequest request,
1294                                               RepositoryGroupConfiguration repositoryGroupConfiguration )
1295         throws DavException
1296     {
1297
1298         try
1299         {
1300             HttpSession session = request.getSession();
1301
1302             Map<String, TemporaryGroupIndex> temporaryGroupIndexMap =
1303                 (Map<String, TemporaryGroupIndex>) session.getAttribute(
1304                     TemporaryGroupIndexSessionCleaner.TEMPORARY_INDEX_SESSION_KEY );
1305             if ( temporaryGroupIndexMap == null )
1306             {
1307                 temporaryGroupIndexMap = new HashMap<>();
1308             }
1309
1310             TemporaryGroupIndex tmp = temporaryGroupIndexMap.get( repositoryGroupConfiguration.getId() );
1311
1312             if ( tmp != null && tmp.getDirectory() != null && tmp.getDirectory().exists() )
1313             {
1314                 if ( System.currentTimeMillis() - tmp.getCreationTime() > (
1315                     repositoryGroupConfiguration.getMergedIndexTtl() * 60 * 1000 ) )
1316                 {
1317                     log.debug( MarkerFactory.getMarker( "group.merged.index" ),
1318                                "tmp group index '{}' is too old so delete it", repositoryGroupConfiguration.getId() );
1319                     indexMerger.cleanTemporaryGroupIndex( tmp );
1320                 }
1321                 else
1322                 {
1323                     log.debug( MarkerFactory.getMarker( "group.merged.index" ),
1324                                "merged index for group '{}' found in cache", repositoryGroupConfiguration.getId() );
1325                     return tmp.getDirectory();
1326                 }
1327             }
1328
1329             Set<String> authzRepos = new HashSet<String>();
1330
1331             String permission = WebdavMethodUtil.getMethodPermission( request.getMethod() );
1332
1333             for ( String repository : repositories )
1334             {
1335                 try
1336                 {
1337                     if ( servletAuth.isAuthorized( activePrincipal, repository, permission ) )
1338                     {
1339                         authzRepos.add( repository );
1340                         authzRepos.addAll( this.repositorySearch.getRemoteIndexingContextIds( repository ) );
1341                     }
1342                 }
1343                 catch ( UnauthorizedException e )
1344                 {
1345                     // TODO: review exception handling
1346
1347                     log.debug( "Skipping repository '{}' for user '{}': {}", repository, activePrincipal,
1348                                e.getMessage() );
1349                 }
1350             }
1351             log.info( "generate temporary merged index for repository group '{}' for repositories '{}'",
1352                       repositoryGroupConfiguration.getId(), authzRepos );
1353
1354             File tempRepoFile = Files.createTempDirectory( "temp" ).toFile();
1355             tempRepoFile.deleteOnExit();
1356
1357             IndexMergerRequest indexMergerRequest =
1358                 new IndexMergerRequest( authzRepos, true, repositoryGroupConfiguration.getId(),
1359                                         repositoryGroupConfiguration.getMergedIndexPath(),
1360                                         repositoryGroupConfiguration.getMergedIndexTtl() ).mergedIndexDirectory(
1361                     tempRepoFile ).temporary( true );
1362
1363             MergedRemoteIndexesTaskRequest taskRequest =
1364                 new MergedRemoteIndexesTaskRequest( indexMergerRequest, indexMerger );
1365
1366             MergedRemoteIndexesTask job = new MergedRemoteIndexesTask( taskRequest );
1367
1368             IndexingContext indexingContext = job.execute().getIndexingContext();
1369
1370             File mergedRepoDir = indexingContext.getIndexDirectoryFile();
1371             TemporaryGroupIndex temporaryGroupIndex =
1372                 new TemporaryGroupIndex( mergedRepoDir, indexingContext.getId(), repositoryGroupConfiguration.getId(),
1373                                          repositoryGroupConfiguration.getMergedIndexTtl() ) //
1374                     .setCreationTime( new Date().getTime() );
1375             temporaryGroupIndexMap.put( repositoryGroupConfiguration.getId(), temporaryGroupIndex );
1376             session.setAttribute( TemporaryGroupIndexSessionCleaner.TEMPORARY_INDEX_SESSION_KEY,
1377                                   temporaryGroupIndexMap );
1378             return mergedRepoDir;
1379         }
1380         catch ( RepositoryAdminException e )
1381         {
1382             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
1383         }
1384         catch ( IndexMergerException e )
1385         {
1386             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
1387         }
1388         catch ( IOException e )
1389         {
1390             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
1391         }
1392     }
1393
1394
1395     public void setServletAuth( ServletAuthenticator servletAuth )
1396     {
1397         this.servletAuth = servletAuth;
1398     }
1399
1400     public void setHttpAuth( HttpAuthenticator httpAuth )
1401     {
1402         this.httpAuth = httpAuth;
1403     }
1404
1405     public void setScheduler( RepositoryArchivaTaskScheduler scheduler )
1406     {
1407         this.scheduler = scheduler;
1408     }
1409
1410     public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
1411     {
1412         this.archivaConfiguration = archivaConfiguration;
1413     }
1414
1415     public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
1416     {
1417         this.repositoryFactory = repositoryFactory;
1418     }
1419
1420     public void setRepositoryRequest( RepositoryRequest repositoryRequest )
1421     {
1422         this.repositoryRequest = repositoryRequest;
1423     }
1424
1425     public void setConnectors( RepositoryProxyConnectors connectors )
1426     {
1427         this.connectors = connectors;
1428     }
1429
1430     public RemoteRepositoryAdmin getRemoteRepositoryAdmin()
1431     {
1432         return remoteRepositoryAdmin;
1433     }
1434
1435     public void setRemoteRepositoryAdmin( RemoteRepositoryAdmin remoteRepositoryAdmin )
1436     {
1437         this.remoteRepositoryAdmin = remoteRepositoryAdmin;
1438     }
1439
1440     public ManagedRepositoryAdmin getManagedRepositoryAdmin()
1441     {
1442         return managedRepositoryAdmin;
1443     }
1444
1445     public void setManagedRepositoryAdmin( ManagedRepositoryAdmin managedRepositoryAdmin )
1446     {
1447         this.managedRepositoryAdmin = managedRepositoryAdmin;
1448     }
1449 }