1 package org.apache.archiva.admin.repository.managed;
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
21 import org.apache.archiva.admin.model.AuditInformation;
22 import org.apache.archiva.admin.model.RepositoryAdminException;
23 import org.apache.archiva.admin.model.beans.ManagedRepository;
24 import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin;
25 import org.apache.archiva.admin.repository.AbstractRepositoryAdmin;
26 import org.apache.archiva.audit.AuditEvent;
27 import org.apache.archiva.common.plexusbridge.MavenIndexerUtils;
28 import org.apache.archiva.common.plexusbridge.PlexusSisuBridge;
29 import org.apache.archiva.common.plexusbridge.PlexusSisuBridgeException;
30 import org.apache.archiva.configuration.Configuration;
31 import org.apache.archiva.configuration.ManagedRepositoryConfiguration;
32 import org.apache.archiva.configuration.ProxyConnectorConfiguration;
33 import org.apache.archiva.configuration.RepositoryGroupConfiguration;
34 import org.apache.archiva.metadata.repository.MetadataRepository;
35 import org.apache.archiva.metadata.repository.MetadataRepositoryException;
36 import org.apache.archiva.metadata.repository.RepositorySession;
37 import org.apache.archiva.metadata.repository.RepositorySessionFactory;
38 import org.apache.archiva.metadata.repository.stats.RepositoryStatisticsManager;
39 import org.apache.archiva.scheduler.repository.RepositoryArchivaTaskScheduler;
40 import org.apache.archiva.scheduler.repository.RepositoryTask;
41 import org.apache.archiva.security.common.ArchivaRoleConstants;
42 import org.apache.commons.io.FileUtils;
43 import org.apache.commons.lang.StringUtils;
44 import org.apache.commons.validator.GenericValidator;
45 import org.apache.maven.index.NexusIndexer;
46 import org.apache.maven.index.context.IndexCreator;
47 import org.apache.maven.index.context.IndexingContext;
48 import org.apache.maven.index.context.UnsupportedExistingLuceneIndexException;
49 import org.codehaus.plexus.redback.role.RoleManager;
50 import org.codehaus.plexus.redback.role.RoleManagerException;
51 import org.codehaus.plexus.taskqueue.TaskQueueException;
52 import org.codehaus.redback.components.scheduler.CronExpressionValidator;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55 import org.springframework.stereotype.Service;
57 import javax.annotation.PostConstruct;
58 import javax.annotation.PreDestroy;
59 import javax.inject.Inject;
60 import javax.inject.Named;
62 import java.io.IOException;
63 import java.net.MalformedURLException;
64 import java.util.ArrayList;
65 import java.util.Arrays;
66 import java.util.HashMap;
67 import java.util.List;
71 * FIXME remove all generic Exception to have usefull ones
72 * FIXME review the staging mechanism to have a per user session one
74 * @author Olivier Lamy
76 @Service( "managedRepositoryAdmin#default" )
77 public class DefaultManagedRepositoryAdmin
78 extends AbstractRepositoryAdmin
79 implements ManagedRepositoryAdmin
82 public static final String REPOSITORY_LOCATION_VALID_EXPRESSION = "^[-a-zA-Z0-9._/~:?!&=\\\\]+$";
84 private Logger log = LoggerFactory.getLogger( getClass() );
86 public static final String STAGE_REPO_ID_END = "-stage";
90 @Named( value = "archivaTaskScheduler#repository" )
91 private RepositoryArchivaTaskScheduler repositoryTaskScheduler;
94 private RepositorySessionFactory repositorySessionFactory;
97 private RepositoryStatisticsManager repositoryStatisticsManager;
100 private PlexusSisuBridge plexusSisuBridge;
103 private MavenIndexerUtils mavenIndexerUtils;
106 protected RoleManager roleManager;
109 List<? extends IndexCreator> indexCreators;
111 NexusIndexer indexer;
114 public void initialize()
115 throws RepositoryAdminException
119 indexCreators = mavenIndexerUtils.getAllIndexCreators();
120 indexer = plexusSisuBridge.lookup( NexusIndexer.class );
122 catch ( PlexusSisuBridgeException e )
124 throw new RepositoryAdminException( e.getMessage(), e );
126 // initialize index context on start
127 for ( ManagedRepository managedRepository : getManagedRepositories() )
129 createIndexContext( managedRepository );
134 public void shutdown()
135 throws RepositoryAdminException
139 // close index on shutdown
140 for ( ManagedRepository managedRepository : getManagedRepositories() )
142 IndexingContext context = indexer.getIndexingContexts().get( managedRepository.getId() );
143 if ( context != null )
145 indexer.removeIndexingContext( context, false );
149 catch ( IOException e )
151 throw new RepositoryAdminException( e.getMessage(), e );
155 public List<ManagedRepository> getManagedRepositories()
156 throws RepositoryAdminException
158 List<ManagedRepositoryConfiguration> managedRepoConfigs =
159 getArchivaConfiguration().getConfiguration().getManagedRepositories();
161 List<ManagedRepository> managedRepos = new ArrayList<ManagedRepository>( managedRepoConfigs.size() );
163 for ( ManagedRepositoryConfiguration repoConfig : managedRepoConfigs )
165 // TODO add staging repo information back too
166 ManagedRepository repo =
167 new ManagedRepository( repoConfig.getId(), repoConfig.getName(), repoConfig.getLocation(),
168 repoConfig.getLayout(), repoConfig.isSnapshots(), repoConfig.isReleases(),
169 repoConfig.isBlockRedeployments(), repoConfig.getRefreshCronExpression(),
170 repoConfig.getIndexDir(), repoConfig.isScanned(), repoConfig.getDaysOlder(),
171 repoConfig.getRetentionCount(), repoConfig.isDeleteReleasedSnapshots(), false );
173 managedRepos.add( repo );
179 public Map<String, ManagedRepository> getManagedRepositoriesAsMap()
180 throws RepositoryAdminException
182 List<ManagedRepository> managedRepositories = getManagedRepositories();
183 Map<String, ManagedRepository> repositoriesMap =
184 new HashMap<String, ManagedRepository>( managedRepositories.size() );
185 for ( ManagedRepository managedRepository : managedRepositories )
187 repositoriesMap.put( managedRepository.getId(), managedRepository );
189 return repositoriesMap;
192 public ManagedRepository getManagedRepository( String repositoryId )
193 throws RepositoryAdminException
195 List<ManagedRepository> repos = getManagedRepositories();
196 for ( ManagedRepository repo : repos )
198 if ( StringUtils.equals( repo.getId(), repositoryId ) )
206 public Boolean addManagedRepository( ManagedRepository managedRepository, boolean needStageRepo,
207 AuditInformation auditInformation )
208 throws RepositoryAdminException
211 getRepositoryCommonValidator().basicValidation( managedRepository, false );
212 triggerAuditEvent( managedRepository.getId(), null, AuditEvent.ADD_MANAGED_REPO, auditInformation );
214 addManagedRepository( managedRepository.getId(), managedRepository.getLayout(), managedRepository.getName(),
215 managedRepository.getLocation(), managedRepository.isBlockRedeployments(),
216 managedRepository.isReleases(), managedRepository.isSnapshots(), needStageRepo,
217 managedRepository.getCronExpression(), managedRepository.getIndexDirectory(),
218 managedRepository.getDaysOlder(), managedRepository.getRetentionCount(),
219 managedRepository.isDeleteReleasedSnapshots(), auditInformation,
220 getArchivaConfiguration().getConfiguration() ) != null;
222 createIndexContext( managedRepository );
227 private ManagedRepositoryConfiguration addManagedRepository( String repoId, String layout, String name,
228 String location, boolean blockRedeployments,
229 boolean releasesIncluded, boolean snapshotsIncluded,
230 boolean stageRepoNeeded, String cronExpression,
231 String indexDir, int daysOlder, int retentionCount,
232 boolean deteleReleasedSnapshots,
233 AuditInformation auditInformation,
234 Configuration config )
235 throws RepositoryAdminException
238 // FIXME : olamy can be empty to avoid scheduled scan ?
239 if ( StringUtils.isNotBlank( cronExpression ) )
241 CronExpressionValidator validator = new CronExpressionValidator();
243 if ( !validator.validate( cronExpression ) )
245 throw new RepositoryAdminException( "Invalid cron expression." );
250 throw new RepositoryAdminException( "Cron expression cannot be empty." );
253 String repoLocation = getRepositoryCommonValidator().removeExpressions( location );
255 if ( !GenericValidator.matchRegexp( repoLocation, REPOSITORY_LOCATION_VALID_EXPRESSION ) )
257 throw new RepositoryAdminException(
258 "Invalid repository location. Directory must only contain alphanumeric characters, equals(=), question-marks(?), "
259 + "exclamation-points(!), ampersands(&), forward-slashes(/), back-slashes(\\), underscores(_), dots(.), colons(:), tildes(~), and dashes(-)." );
262 ManagedRepositoryConfiguration repository = new ManagedRepositoryConfiguration();
264 repository.setId( repoId );
265 repository.setBlockRedeployments( blockRedeployments );
266 repository.setReleases( releasesIncluded );
267 repository.setSnapshots( snapshotsIncluded );
268 repository.setName( name );
269 repository.setLocation( repoLocation );
270 repository.setLayout( layout );
271 repository.setRefreshCronExpression( cronExpression );
272 repository.setIndexDir( indexDir );
273 repository.setDaysOlder( daysOlder );
274 repository.setRetentionCount( retentionCount );
275 repository.setDeleteReleasedSnapshots( deteleReleasedSnapshots );
276 repository.setIndexDir( indexDir );
280 addRepository( repository, config );
281 addRepositoryRoles( repository );
283 if ( stageRepoNeeded )
285 ManagedRepositoryConfiguration stagingRepository = getStageRepoConfig( repository );
286 addRepository( stagingRepository, config );
287 addRepositoryRoles( stagingRepository );
288 triggerAuditEvent( stagingRepository.getId(), null, AuditEvent.ADD_MANAGED_REPO, auditInformation );
291 catch ( RoleManagerException e )
293 throw new RepositoryAdminException( "failed to add repository roles " + e.getMessage(), e );
295 catch ( IOException e )
297 throw new RepositoryAdminException( "failed to add repository " + e.getMessage(), e );
300 saveConfiguration( config );
302 //MRM-1342 Repository statistics report doesn't appear to be working correctly
303 //scan repository when adding of repository is successful
306 scanRepository( repoId, true );
307 // olamy no need of scanning staged repo
309 if ( stageRepoNeeded )
311 ManagedRepositoryConfiguration stagingRepository = getStageRepoConfig( repository );
312 scanRepository( stagingRepository.getId(), true );
315 catch ( Exception e )
317 log.warn( new StringBuilder( "Unable to scan repository [" ).append( repoId ).append( "]: " ).append(
318 e.getMessage() ).toString(), e );
324 public Boolean deleteManagedRepository( String repositoryId, AuditInformation auditInformation,
325 boolean deleteContent )
326 throws RepositoryAdminException
328 Configuration config = getArchivaConfiguration().getConfiguration();
330 ManagedRepositoryConfiguration repository = config.findManagedRepositoryById( repositoryId );
332 if ( repository == null )
334 throw new RepositoryAdminException( "A repository with that id does not exist" );
337 triggerAuditEvent( repositoryId, null, AuditEvent.DELETE_MANAGED_REPO, auditInformation );
339 deleteManagedRepository( repository, deleteContent, config, false );
341 // stage repo exists ?
342 ManagedRepositoryConfiguration stagingRepository =
343 getArchivaConfiguration().getConfiguration().findManagedRepositoryById( repositoryId + STAGE_REPO_ID_END );
344 if ( stagingRepository != null )
346 // do not trigger event when deleting the staged one
347 deleteManagedRepository( stagingRepository, deleteContent, config, true );
352 saveConfiguration( config );
354 catch ( Exception e )
356 throw new RepositoryAdminException( "Error saving configuration for delete action" + e.getMessage() );
362 private Boolean deleteManagedRepository( ManagedRepositoryConfiguration repository, boolean deleteContent,
363 Configuration config, boolean stagedOne )
364 throws RepositoryAdminException
369 NexusIndexer nexusIndexer = plexusSisuBridge.lookup( NexusIndexer.class );
371 IndexingContext context = nexusIndexer.getIndexingContexts().get( repository.getId() );
372 if ( context != null )
374 // delete content only if directory exists
375 nexusIndexer.removeIndexingContext( context,
376 deleteContent && context.getIndexDirectoryFile().exists() );
379 catch ( PlexusSisuBridgeException e )
381 throw new RepositoryAdminException( e.getMessage(), e );
383 catch ( IOException e )
385 throw new RepositoryAdminException( e.getMessage(), e );
389 RepositorySession repositorySession = getRepositorySessionFactory().createSession();
392 MetadataRepository metadataRepository = repositorySession.getRepository();
393 metadataRepository.removeRepository( repository.getId() );
394 log.debug( "call repositoryStatisticsManager.deleteStatistics" );
395 getRepositoryStatisticsManager().deleteStatistics( metadataRepository, repository.getId() );
396 repositorySession.save();
398 catch ( MetadataRepositoryException e )
400 throw new RepositoryAdminException( e.getMessage(), e );
404 repositorySession.close();
407 config.removeManagedRepository( repository );
411 // TODO could be async ? as directory can be huge
412 File dir = new File( repository.getLocation() );
413 if ( !FileUtils.deleteQuietly( dir ) )
415 throw new RepositoryAdminException( "Cannot delete repository " + dir );
419 // olamy: copy list for reading as a unit test in webapp fail with ConcurrentModificationException
420 List<ProxyConnectorConfiguration> proxyConnectors =
421 new ArrayList<ProxyConnectorConfiguration>( config.getProxyConnectors() );
422 for ( ProxyConnectorConfiguration proxyConnector : proxyConnectors )
424 if ( StringUtils.equals( proxyConnector.getSourceRepoId(), repository.getId() ) )
426 config.removeProxyConnector( proxyConnector );
430 Map<String, List<String>> repoToGroupMap = config.getRepositoryToGroupMap();
431 if ( repoToGroupMap != null )
433 if ( repoToGroupMap.containsKey( repository.getId() ) )
435 List<String> repoGroups = repoToGroupMap.get( repository.getId() );
436 for ( String repoGroup : repoGroups )
438 // copy to prevent UnsupportedOperationException
439 RepositoryGroupConfiguration repositoryGroupConfiguration =
440 config.findRepositoryGroupById( repoGroup );
441 List<String> repos = new ArrayList<String>( repositoryGroupConfiguration.getRepositories() );
442 config.removeRepositoryGroup( repositoryGroupConfiguration );
443 repos.remove( repository.getId() );
444 repositoryGroupConfiguration.setRepositories( repos );
445 config.addRepositoryGroup( repositoryGroupConfiguration );
452 removeRepositoryRoles( repository );
454 catch ( RoleManagerException e )
456 throw new RepositoryAdminException(
457 "fail to remove repository roles for repository " + repository.getId() + " : " + e.getMessage(), e );
460 saveConfiguration( config );
466 public Boolean updateManagedRepository( ManagedRepository managedRepository, boolean needStageRepo,
467 AuditInformation auditInformation, boolean resetStats )
468 throws RepositoryAdminException
471 log.debug( "updateManagedConfiguration repo {} needStage {} resetStats {} ",
472 Arrays.asList( managedRepository, needStageRepo, resetStats ).toArray() );
474 // Ensure that the fields are valid.
476 getRepositoryCommonValidator().basicValidation( managedRepository, true );
478 Configuration configuration = getArchivaConfiguration().getConfiguration();
480 ManagedRepositoryConfiguration toremove = configuration.findManagedRepositoryById( managedRepository.getId() );
482 if ( toremove != null )
484 configuration.removeManagedRepository( toremove );
487 ManagedRepositoryConfiguration stagingRepository = getStageRepoConfig( toremove );
489 // TODO remove content from old if path has changed !!!!!
491 if ( stagingRepository != null )
493 configuration.removeManagedRepository( stagingRepository );
496 ManagedRepositoryConfiguration managedRepositoryConfiguration =
497 addManagedRepository( managedRepository.getId(), managedRepository.getLayout(), managedRepository.getName(),
498 managedRepository.getLocation(), managedRepository.isBlockRedeployments(),
499 managedRepository.isReleases(), managedRepository.isSnapshots(), needStageRepo,
500 managedRepository.getCronExpression(), managedRepository.getIndexDirectory(),
501 managedRepository.getDaysOlder(), managedRepository.getRetentionCount(),
502 managedRepository.isDeleteReleasedSnapshots(), auditInformation,
503 getArchivaConfiguration().getConfiguration() );
505 // Save the repository configuration.
506 RepositorySession repositorySession = getRepositorySessionFactory().createSession();
510 triggerAuditEvent( managedRepositoryConfiguration.getId(), null, AuditEvent.MODIFY_MANAGED_REPO,
513 saveConfiguration( this.getArchivaConfiguration().getConfiguration() );
516 log.debug( "call repositoryStatisticsManager.deleteStatistics" );
517 getRepositoryStatisticsManager().deleteStatistics( repositorySession.getRepository(),
518 managedRepositoryConfiguration.getId() );
519 repositorySession.save();
523 catch ( MetadataRepositoryException e )
525 throw new RepositoryAdminException( e.getMessage(), e );
529 repositorySession.close();
531 createIndexContext( managedRepository );
535 //--------------------------
537 //--------------------------
540 protected void addRepository( ManagedRepositoryConfiguration repository, Configuration configuration )
541 throws RepositoryAdminException, IOException
543 // Normalize the path
544 File file = new File( repository.getLocation() );
545 repository.setLocation( file.getCanonicalPath() );
546 if ( !file.exists() )
550 if ( !file.exists() || !file.isDirectory() )
552 throw new RepositoryAdminException(
553 "Unable to add repository - no write access, can not create the root directory: " + file );
556 configuration.addManagedRepository( repository );
560 public IndexingContext createIndexContext( ManagedRepository repository )
561 throws RepositoryAdminException
566 IndexingContext context = indexer.getIndexingContexts().get( repository.getId() );
568 if ( context != null )
570 log.debug( "skip adding repository with id {} as already exists", repository.getId() );
574 String indexDir = repository.getIndexDirectory();
575 File managedRepository = new File( repository.getLocation() );
577 File indexDirectory = null;
578 if ( indexDir != null && !"".equals( indexDir ) )
580 indexDirectory = new File( repository.getIndexDirectory() );
581 if ( !indexDirectory.isAbsolute() )
583 indexDirectory = new File( managedRepository, repository.getIndexDirectory() );
588 indexDirectory = new File( managedRepository, ".indexer" );
591 if ( !indexDirectory.exists() )
593 indexDirectory.mkdirs();
597 indexer.addIndexingContext( repository.getId(), repository.getId(), managedRepository, indexDirectory,
598 managedRepository.toURI().toURL().toExternalForm(),
599 indexDirectory.toURI().toURL().toString(), indexCreators );
601 context.setSearchable( repository.isScanned() );
604 catch ( MalformedURLException e )
606 throw new RepositoryAdminException( e.getMessage(), e );
608 catch ( IOException e )
610 throw new RepositoryAdminException( e.getMessage(), e );
612 catch ( UnsupportedExistingLuceneIndexException e )
614 throw new RepositoryAdminException( e.getMessage(), e );
618 private ManagedRepositoryConfiguration getStageRepoConfig( ManagedRepositoryConfiguration repository )
620 ManagedRepositoryConfiguration stagingRepository = new ManagedRepositoryConfiguration();
621 stagingRepository.setId( repository.getId() + STAGE_REPO_ID_END );
622 stagingRepository.setLayout( repository.getLayout() );
623 stagingRepository.setName( repository.getName() + STAGE_REPO_ID_END );
624 stagingRepository.setBlockRedeployments( repository.isBlockRedeployments() );
625 stagingRepository.setDaysOlder( repository.getDaysOlder() );
626 stagingRepository.setDeleteReleasedSnapshots( repository.isDeleteReleasedSnapshots() );
627 stagingRepository.setIndexDir( repository.getIndexDir() );
628 String path = repository.getLocation();
629 int lastIndex = path.lastIndexOf( '/' );
630 stagingRepository.setLocation( path.substring( 0, lastIndex ) + "/" + stagingRepository.getId() );
631 stagingRepository.setRefreshCronExpression( repository.getRefreshCronExpression() );
632 stagingRepository.setReleases( repository.isReleases() );
633 stagingRepository.setRetentionCount( repository.getRetentionCount() );
634 stagingRepository.setScanned( repository.isScanned() );
635 stagingRepository.setSnapshots( repository.isSnapshots() );
636 return stagingRepository;
639 public Boolean scanRepository( String repositoryId, boolean fullScan )
641 if ( getRepositoryTaskScheduler().isProcessingRepositoryTask( repositoryId ) )
643 log.info( "scanning of repository with id {} already scheduled", repositoryId );
645 RepositoryTask task = new RepositoryTask();
646 task.setRepositoryId( repositoryId );
647 task.setScanAll( fullScan );
650 getRepositoryTaskScheduler().queueTask( task );
652 catch ( TaskQueueException e )
654 log.error( "failed to schedule scanning of repo with id {}", repositoryId, e );
660 protected void addRepositoryRoles( ManagedRepositoryConfiguration newRepository )
661 throws RoleManagerException
663 String repoId = newRepository.getId();
665 // TODO: double check these are configured on start up
666 // TODO: belongs in the business logic
668 if ( !getRoleManager().templatedRoleExists( ArchivaRoleConstants.TEMPLATE_REPOSITORY_OBSERVER, repoId ) )
670 getRoleManager().createTemplatedRole( ArchivaRoleConstants.TEMPLATE_REPOSITORY_OBSERVER, repoId );
673 if ( !getRoleManager().templatedRoleExists( ArchivaRoleConstants.TEMPLATE_REPOSITORY_MANAGER, repoId ) )
675 getRoleManager().createTemplatedRole( ArchivaRoleConstants.TEMPLATE_REPOSITORY_MANAGER, repoId );
679 protected void removeRepositoryRoles( ManagedRepositoryConfiguration existingRepository )
680 throws RoleManagerException
682 String repoId = existingRepository.getId();
684 if ( getRoleManager().templatedRoleExists( ArchivaRoleConstants.TEMPLATE_REPOSITORY_MANAGER, repoId ) )
686 getRoleManager().removeTemplatedRole( ArchivaRoleConstants.TEMPLATE_REPOSITORY_MANAGER, repoId );
689 if ( getRoleManager().templatedRoleExists( ArchivaRoleConstants.TEMPLATE_REPOSITORY_OBSERVER, repoId ) )
691 getRoleManager().removeTemplatedRole( ArchivaRoleConstants.TEMPLATE_REPOSITORY_OBSERVER, repoId );
694 log.debug( "removed user roles associated with repository {}", repoId );
697 //--------------------------
699 //--------------------------
702 public RoleManager getRoleManager()
707 public void setRoleManager( RoleManager roleManager )
709 this.roleManager = roleManager;
712 public RepositoryStatisticsManager getRepositoryStatisticsManager()
714 return repositoryStatisticsManager;
717 public void setRepositoryStatisticsManager( RepositoryStatisticsManager repositoryStatisticsManager )
719 this.repositoryStatisticsManager = repositoryStatisticsManager;
722 public RepositorySessionFactory getRepositorySessionFactory()
724 return repositorySessionFactory;
727 public void setRepositorySessionFactory( RepositorySessionFactory repositorySessionFactory )
729 this.repositorySessionFactory = repositorySessionFactory;
733 public RepositoryArchivaTaskScheduler getRepositoryTaskScheduler()
735 return repositoryTaskScheduler;
738 public void setRepositoryTaskScheduler( RepositoryArchivaTaskScheduler repositoryTaskScheduler )
740 this.repositoryTaskScheduler = repositoryTaskScheduler;