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