001package org.apache.archiva.indexer.maven.merger; 002/* 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 */ 020 021import org.apache.archiva.common.utils.FileUtils; 022import org.apache.archiva.indexer.UnsupportedBaseContextException; 023import org.apache.archiva.indexer.merger.IndexMerger; 024import org.apache.archiva.indexer.merger.IndexMergerException; 025import org.apache.archiva.indexer.merger.IndexMergerRequest; 026import org.apache.archiva.indexer.merger.TemporaryGroupIndex; 027import org.apache.archiva.repository.RepositoryRegistry; 028import org.apache.archiva.repository.RepositoryType; 029import org.apache.commons.lang.time.StopWatch; 030import org.apache.maven.index.Indexer; 031import org.apache.maven.index.context.ContextMemberProvider; 032import org.apache.maven.index.context.IndexCreator; 033import org.apache.maven.index.context.IndexingContext; 034import org.apache.maven.index.context.StaticContextMemberProvider; 035import org.apache.maven.index.packer.IndexPacker; 036import org.apache.maven.index.packer.IndexPackingRequest; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039import org.springframework.scheduling.annotation.Async; 040import org.springframework.stereotype.Service; 041 042import javax.inject.Inject; 043import java.io.IOException; 044import java.nio.file.Files; 045import java.nio.file.Path; 046import java.util.Collection; 047import java.util.List; 048import java.util.Objects; 049import java.util.Optional; 050import java.util.concurrent.CopyOnWriteArrayList; 051import java.util.stream.Collectors; 052 053/** 054 * @author Olivier Lamy 055 * @since 1.4-M2 056 */ 057@Service("indexMerger#default") 058public class DefaultIndexMerger 059 implements IndexMerger 060{ 061 062 @Inject 063 RepositoryRegistry repositoryRegistry; 064 065 private Logger log = LoggerFactory.getLogger( getClass() ); 066 067 068 private final IndexPacker indexPacker; 069 070 private Indexer indexer; 071 072 private final List<IndexCreator> indexCreators; 073 074 private List<TemporaryGroupIndex> temporaryGroupIndexes = new CopyOnWriteArrayList<>(); 075 076 private List<IndexingContext> temporaryContextes = new CopyOnWriteArrayList<>( ); 077 078 private List<String> runningGroups = new CopyOnWriteArrayList<>(); 079 080 @Inject 081 public DefaultIndexMerger( Indexer indexer, IndexPacker indexPacker, List<IndexCreator> indexCreators ) 082 { 083 this.indexer = indexer; 084 this.indexPacker = indexPacker; 085 this.indexCreators = indexCreators; 086 } 087 088 @Override 089 public IndexingContext buildMergedIndex( IndexMergerRequest indexMergerRequest ) 090 throws IndexMergerException 091 { 092 String groupId = indexMergerRequest.getGroupId(); 093 094 if ( runningGroups.contains( groupId ) ) 095 { 096 log.info( "skip build merge remote indexes for id: '{}' as already running", groupId ); 097 return null; 098 } 099 100 runningGroups.add( groupId ); 101 102 StopWatch stopWatch = new StopWatch(); 103 stopWatch.reset(); 104 stopWatch.start(); 105 106 Path mergedIndexDirectory = indexMergerRequest.getMergedIndexDirectory(); 107 108 String tempRepoId = mergedIndexDirectory.getFileName().toString(); 109 110 try 111 { 112 Path indexLocation = mergedIndexDirectory.resolve( indexMergerRequest.getMergedIndexPath() ); 113 114 List<IndexingContext> members = indexMergerRequest.getRepositoriesIds( ).stream( ).map( id -> 115 repositoryRegistry.getRepository( id ) ).filter( repo -> repo.getType().equals( RepositoryType.MAVEN ) ) 116 .map( repo -> { 117 try 118 { 119 return repo.getIndexingContext().getBaseContext( IndexingContext.class ); 120 } 121 catch ( UnsupportedBaseContextException e ) 122 { 123 return null; 124 // Ignore 125 } 126 } ).filter( Objects::nonNull ).collect( Collectors.toList() ); 127 ContextMemberProvider memberProvider = new StaticContextMemberProvider(members); 128 IndexingContext mergedCtx = indexer.createMergedIndexingContext( tempRepoId, tempRepoId, mergedIndexDirectory.toFile(), 129 indexLocation.toFile(), true, memberProvider); 130 mergedCtx.optimize(); 131 132 if ( indexMergerRequest.isPackIndex() ) 133 { 134 IndexPackingRequest request = new IndexPackingRequest( mergedCtx, // 135 mergedCtx.acquireIndexSearcher().getIndexReader(), // 136 indexLocation.toFile() ); 137 indexPacker.packIndex( request ); 138 } 139 140 if ( indexMergerRequest.isTemporary() ) 141 { 142 temporaryGroupIndexes.add( new TemporaryGroupIndex( mergedIndexDirectory, tempRepoId, groupId, 143 indexMergerRequest.getMergedIndexTtl() ) ); 144 temporaryContextes.add(mergedCtx); 145 } 146 stopWatch.stop(); 147 log.info( "merged index for repos {} in {} s", indexMergerRequest.getRepositoriesIds(), 148 stopWatch.getTime() ); 149 return mergedCtx; 150 } 151 catch ( IOException e) 152 { 153 throw new IndexMergerException( e.getMessage(), e ); 154 } 155 finally 156 { 157 runningGroups.remove( groupId ); 158 } 159 } 160 161 @Async 162 @Override 163 public void cleanTemporaryGroupIndex( TemporaryGroupIndex temporaryGroupIndex ) 164 { 165 if ( temporaryGroupIndex == null ) 166 { 167 return; 168 } 169 170 try 171 { 172 173 Optional<IndexingContext> ctxOpt = temporaryContextes.stream( ).filter( ctx -> ctx.getId( ).equals( temporaryGroupIndex.getIndexId( ) ) ).findFirst( ); 174 if (ctxOpt.isPresent()) { 175 IndexingContext ctx = ctxOpt.get(); 176 indexer.closeIndexingContext( ctx, true ); 177 temporaryGroupIndexes.remove( temporaryGroupIndex ); 178 temporaryContextes.remove( ctx ); 179 Path directory = temporaryGroupIndex.getDirectory(); 180 if ( directory != null && Files.exists(directory) ) 181 { 182 FileUtils.deleteDirectory( directory ); 183 } 184 } 185 } 186 catch ( IOException e ) 187 { 188 log.warn( "fail to delete temporary group index {}", temporaryGroupIndex.getIndexId(), e ); 189 } 190 } 191 192 @Override 193 public Collection<TemporaryGroupIndex> getTemporaryGroupIndexes() 194 { 195 return this.temporaryGroupIndexes; 196 } 197}