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 private 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 private 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 nexusIndexer.removeIndexingContext( context, deleteContent );
377 catch ( PlexusSisuBridgeException e )
379 throw new RepositoryAdminException( e.getMessage(), e );
381 catch ( IOException e )
383 throw new RepositoryAdminException( e.getMessage(), e );
387 RepositorySession repositorySession = getRepositorySessionFactory().createSession();
390 MetadataRepository metadataRepository = repositorySession.getRepository();
391 metadataRepository.removeRepository( repository.getId() );
392 log.debug( "call repositoryStatisticsManager.deleteStatistics" );
393 getRepositoryStatisticsManager().deleteStatistics( metadataRepository, repository.getId() );
394 repositorySession.save();
396 catch ( MetadataRepositoryException e )
398 throw new RepositoryAdminException( e.getMessage(), e );
402 repositorySession.close();
405 config.removeManagedRepository( repository );
409 // TODO could be async ? as directory can be huge
410 File dir = new File( repository.getLocation() );
411 if ( !FileUtils.deleteQuietly( dir ) )
413 throw new RepositoryAdminException( "Cannot delete repository " + dir );
417 // olamy: copy list for reading as a unit test in webapp fail with ConcurrentModificationException
418 List<ProxyConnectorConfiguration> proxyConnectors =
419 new ArrayList<ProxyConnectorConfiguration>( config.getProxyConnectors() );
420 for ( ProxyConnectorConfiguration proxyConnector : proxyConnectors )
422 if ( StringUtils.equals( proxyConnector.getSourceRepoId(), repository.getId() ) )
424 config.removeProxyConnector( proxyConnector );
428 Map<String, List<String>> repoToGroupMap = config.getRepositoryToGroupMap();
429 if ( repoToGroupMap != null )
431 if ( repoToGroupMap.containsKey( repository.getId() ) )
433 List<String> repoGroups = repoToGroupMap.get( repository.getId() );
434 for ( String repoGroup : repoGroups )
436 // copy to prevent UnsupportedOperationException
437 RepositoryGroupConfiguration repositoryGroupConfiguration =
438 config.findRepositoryGroupById( repoGroup );
439 List<String> repos = new ArrayList<String>( repositoryGroupConfiguration.getRepositories() );
440 config.removeRepositoryGroup( repositoryGroupConfiguration );
441 repos.remove( repository.getId() );
442 repositoryGroupConfiguration.setRepositories( repos );
443 config.addRepositoryGroup( repositoryGroupConfiguration );
450 removeRepositoryRoles( repository );
452 catch ( RoleManagerException e )
454 throw new RepositoryAdminException(
455 "fail to remove repository roles for repository " + repository.getId() + " : " + e.getMessage(), e );
458 saveConfiguration( config );
464 public Boolean updateManagedRepository( ManagedRepository managedRepository, boolean needStageRepo,
465 AuditInformation auditInformation, boolean resetStats )
466 throws RepositoryAdminException
469 log.debug( "updateManagedConfiguration repo {} needStage {} resetStats {} ",
470 Arrays.asList( managedRepository, needStageRepo, resetStats ).toArray() );
472 // Ensure that the fields are valid.
474 getRepositoryCommonValidator().basicValidation( managedRepository, true );
476 Configuration configuration = getArchivaConfiguration().getConfiguration();
478 ManagedRepositoryConfiguration toremove = configuration.findManagedRepositoryById( managedRepository.getId() );
480 if ( toremove != null )
482 configuration.removeManagedRepository( toremove );
485 ManagedRepositoryConfiguration stagingRepository = getStageRepoConfig( toremove );
487 // TODO remove content from old if path has changed !!!!!
489 if ( stagingRepository != null )
491 configuration.removeManagedRepository( stagingRepository );
494 ManagedRepositoryConfiguration managedRepositoryConfiguration =
495 addManagedRepository( managedRepository.getId(), managedRepository.getLayout(), managedRepository.getName(),
496 managedRepository.getLocation(), managedRepository.isBlockRedeployments(),
497 managedRepository.isReleases(), managedRepository.isSnapshots(), needStageRepo,
498 managedRepository.getCronExpression(), managedRepository.getIndexDirectory(),
499 managedRepository.getDaysOlder(), managedRepository.getRetentionCount(),
500 managedRepository.isDeleteReleasedSnapshots(), auditInformation,
501 getArchivaConfiguration().getConfiguration() );
503 // Save the repository configuration.
504 RepositorySession repositorySession = getRepositorySessionFactory().createSession();
508 triggerAuditEvent( managedRepositoryConfiguration.getId(), null, AuditEvent.MODIFY_MANAGED_REPO,
511 saveConfiguration( this.getArchivaConfiguration().getConfiguration() );
514 log.debug( "call repositoryStatisticsManager.deleteStatistics" );
515 getRepositoryStatisticsManager().deleteStatistics( repositorySession.getRepository(),
516 managedRepositoryConfiguration.getId() );
517 repositorySession.save();
521 catch ( MetadataRepositoryException e )
523 throw new RepositoryAdminException( e.getMessage(), e );
527 repositorySession.close();
529 createIndexContext( managedRepository );
533 //--------------------------
535 //--------------------------
538 protected void addRepository( ManagedRepositoryConfiguration repository, Configuration configuration )
539 throws RepositoryAdminException, IOException
541 // Normalize the path
542 File file = new File( repository.getLocation() );
543 repository.setLocation( file.getCanonicalPath() );
544 if ( !file.exists() )
548 if ( !file.exists() || !file.isDirectory() )
550 throw new RepositoryAdminException(
551 "Unable to add repository - no write access, can not create the root directory: " + file );
554 configuration.addManagedRepository( repository );
558 public IndexingContext createIndexContext( ManagedRepository repository )
559 throws RepositoryAdminException
564 IndexingContext context = indexer.getIndexingContexts().get( repository.getId() );
566 if ( context != null )
568 log.debug( "skip adding repository with id {} as already exists", repository.getId() );
572 String indexDir = repository.getIndexDirectory();
573 File managedRepository = new File( repository.getLocation() );
575 File indexDirectory = null;
576 if ( indexDir != null && !"".equals( indexDir ) )
578 indexDirectory = new File( repository.getIndexDirectory() );
579 if ( !indexDirectory.isAbsolute() )
581 indexDirectory = new File( managedRepository, repository.getIndexDirectory() );
586 indexDirectory = new File( managedRepository, ".indexer" );
590 indexer.addIndexingContext( repository.getId(), repository.getId(), managedRepository, indexDirectory,
591 managedRepository.toURI().toURL().toExternalForm(),
592 indexDirectory.toURI().toURL().toString(), indexCreators );
594 context.setSearchable( repository.isScanned() );
597 catch ( MalformedURLException e )
599 throw new RepositoryAdminException( e.getMessage(), e );
601 catch ( IOException e )
603 throw new RepositoryAdminException( e.getMessage(), e );
605 catch ( UnsupportedExistingLuceneIndexException e )
607 throw new RepositoryAdminException( e.getMessage(), e );
611 private ManagedRepositoryConfiguration getStageRepoConfig( ManagedRepositoryConfiguration repository )
613 ManagedRepositoryConfiguration stagingRepository = new ManagedRepositoryConfiguration();
614 stagingRepository.setId( repository.getId() + STAGE_REPO_ID_END );
615 stagingRepository.setLayout( repository.getLayout() );
616 stagingRepository.setName( repository.getName() + STAGE_REPO_ID_END );
617 stagingRepository.setBlockRedeployments( repository.isBlockRedeployments() );
618 stagingRepository.setDaysOlder( repository.getDaysOlder() );
619 stagingRepository.setDeleteReleasedSnapshots( repository.isDeleteReleasedSnapshots() );
620 stagingRepository.setIndexDir( repository.getIndexDir() );
621 String path = repository.getLocation();
622 int lastIndex = path.lastIndexOf( '/' );
623 stagingRepository.setLocation( path.substring( 0, lastIndex ) + "/" + stagingRepository.getId() );
624 stagingRepository.setRefreshCronExpression( repository.getRefreshCronExpression() );
625 stagingRepository.setReleases( repository.isReleases() );
626 stagingRepository.setRetentionCount( repository.getRetentionCount() );
627 stagingRepository.setScanned( repository.isScanned() );
628 stagingRepository.setSnapshots( repository.isSnapshots() );
629 return stagingRepository;
632 public Boolean scanRepository( String repositoryId, boolean fullScan )
634 if ( getRepositoryTaskScheduler().isProcessingRepositoryTask( repositoryId ) )
636 log.info( "scanning of repository with id {} already scheduled", repositoryId );
638 RepositoryTask task = new RepositoryTask();
639 task.setRepositoryId( repositoryId );
640 task.setScanAll( fullScan );
643 getRepositoryTaskScheduler().queueTask( task );
645 catch ( TaskQueueException e )
647 log.error( "failed to schedule scanning of repo with id {}", repositoryId, e );
653 protected void addRepositoryRoles( ManagedRepositoryConfiguration newRepository )
654 throws RoleManagerException
656 String repoId = newRepository.getId();
658 // TODO: double check these are configured on start up
659 // TODO: belongs in the business logic
661 if ( !getRoleManager().templatedRoleExists( ArchivaRoleConstants.TEMPLATE_REPOSITORY_OBSERVER, repoId ) )
663 getRoleManager().createTemplatedRole( ArchivaRoleConstants.TEMPLATE_REPOSITORY_OBSERVER, repoId );
666 if ( !getRoleManager().templatedRoleExists( ArchivaRoleConstants.TEMPLATE_REPOSITORY_MANAGER, repoId ) )
668 getRoleManager().createTemplatedRole( ArchivaRoleConstants.TEMPLATE_REPOSITORY_MANAGER, repoId );
672 protected void removeRepositoryRoles( ManagedRepositoryConfiguration existingRepository )
673 throws RoleManagerException
675 String repoId = existingRepository.getId();
677 if ( getRoleManager().templatedRoleExists( ArchivaRoleConstants.TEMPLATE_REPOSITORY_MANAGER, repoId ) )
679 getRoleManager().removeTemplatedRole( ArchivaRoleConstants.TEMPLATE_REPOSITORY_MANAGER, repoId );
682 if ( getRoleManager().templatedRoleExists( ArchivaRoleConstants.TEMPLATE_REPOSITORY_OBSERVER, repoId ) )
684 getRoleManager().removeTemplatedRole( ArchivaRoleConstants.TEMPLATE_REPOSITORY_OBSERVER, repoId );
687 log.debug( "removed user roles associated with repository {}", repoId );
690 //--------------------------
692 //--------------------------
695 public RoleManager getRoleManager()
700 public void setRoleManager( RoleManager roleManager )
702 this.roleManager = roleManager;
705 public RepositoryStatisticsManager getRepositoryStatisticsManager()
707 return repositoryStatisticsManager;
710 public void setRepositoryStatisticsManager( RepositoryStatisticsManager repositoryStatisticsManager )
712 this.repositoryStatisticsManager = repositoryStatisticsManager;
715 public RepositorySessionFactory getRepositorySessionFactory()
717 return repositorySessionFactory;
720 public void setRepositorySessionFactory( RepositorySessionFactory repositorySessionFactory )
722 this.repositorySessionFactory = repositorySessionFactory;
726 public RepositoryArchivaTaskScheduler getRepositoryTaskScheduler()
728 return repositoryTaskScheduler;
731 public void setRepositoryTaskScheduler( RepositoryArchivaTaskScheduler repositoryTaskScheduler )
733 this.repositoryTaskScheduler = repositoryTaskScheduler;