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