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