1 package org.apache.maven.index.incremental;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.text.ParseException;
25 import java.text.SimpleDateFormat;
26 import java.util.ArrayList;
27 import java.util.Date;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Map.Entry;
32 import java.util.Properties;
33 import java.util.Set;
34 import java.util.TimeZone;
35 import java.util.TreeMap;
36
37 import javax.inject.Named;
38 import javax.inject.Singleton;
39
40 import org.apache.lucene.document.Document;
41 import org.apache.lucene.index.IndexReader;
42 import org.apache.lucene.index.MultiBits;
43 import org.apache.lucene.util.Bits;
44 import org.apache.maven.index.ArtifactInfo;
45 import org.apache.maven.index.context.IndexingContext;
46 import org.apache.maven.index.packer.IndexPackingRequest;
47 import org.apache.maven.index.updater.IndexUpdateRequest;
48 import org.codehaus.plexus.util.StringUtils;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 @Singleton
53 @Named
54 public class DefaultIncrementalHandler
55 implements IncrementalHandler
56 {
57
58 private final Logger logger = LoggerFactory.getLogger( getClass() );
59
60 protected Logger getLogger()
61 {
62 return logger;
63 }
64
65 public List<Integer> getIncrementalUpdates( IndexPackingRequest request, Properties properties )
66 throws IOException
67 {
68 getLogger().debug( "Handling Incremental Updates" );
69
70 if ( !validateProperties( properties ) )
71 {
72 getLogger().debug( "Invalid properties found, resetting them and doing no incremental packing." );
73 return null;
74 }
75
76
77
78 List<Integer> chunk =
79 getIndexChunk( request, parse( properties.getProperty( IndexingContext.INDEX_TIMESTAMP ) ) );
80
81 getLogger().debug( "Found " + chunk.size() + " differences to put in incremental index." );
82
83
84 if ( chunk.size() > 0 )
85 {
86 updateProperties( properties, request );
87 }
88
89 cleanUpIncrementalChunks( request, properties );
90
91 return chunk;
92 }
93
94 public List<String> loadRemoteIncrementalUpdates( IndexUpdateRequest request, Properties localProperties,
95 Properties remoteProperties )
96 throws IOException
97 {
98 List<String> filenames = null;
99
100 if ( canRetrieveAllChunks( localProperties, remoteProperties ) )
101 {
102 filenames = new ArrayList<>();
103
104 int maxCounter = Integer.parseInt( remoteProperties.getProperty( IndexingContext.INDEX_CHUNK_COUNTER ) );
105 int currentCounter = Integer.parseInt( localProperties.getProperty( IndexingContext.INDEX_CHUNK_COUNTER ) );
106
107
108 currentCounter++;
109
110 while ( currentCounter <= maxCounter )
111 {
112 filenames.add( IndexingContext.INDEX_FILE_PREFIX + "." + currentCounter++ + ".gz" );
113 }
114 }
115
116 return filenames;
117 }
118
119 private boolean validateProperties( Properties properties )
120 {
121 if ( properties == null || properties.isEmpty() )
122 {
123 return false;
124 }
125
126 if ( properties.getProperty( IndexingContext.INDEX_TIMESTAMP ) == null )
127 {
128 return false;
129 }
130
131 if ( parse( properties.getProperty( IndexingContext.INDEX_TIMESTAMP ) ) == null )
132 {
133 return false;
134 }
135
136 initializeProperties( properties );
137
138 return true;
139 }
140
141 public void initializeProperties( Properties properties )
142 {
143 if ( properties.getProperty( IndexingContext.INDEX_CHAIN_ID ) == null )
144 {
145 properties.setProperty( IndexingContext.INDEX_CHAIN_ID, Long.toString( new Date().getTime() ) );
146 properties.remove( IndexingContext.INDEX_CHUNK_COUNTER );
147 }
148
149 if ( properties.getProperty( IndexingContext.INDEX_CHUNK_COUNTER ) == null )
150 {
151 properties.setProperty( IndexingContext.INDEX_CHUNK_COUNTER, "0" );
152 }
153 }
154
155 private List<Integer> getIndexChunk( IndexPackingRequest request, Date timestamp )
156 throws IOException
157 {
158 final List<Integer> chunk = new ArrayList<>();
159 final IndexReader r = request.getIndexReader();
160 Bits liveDocs = MultiBits.getLiveDocs( r );
161 for ( int i = 0; i < r.maxDoc(); i++ )
162 {
163 if ( liveDocs == null || liveDocs.get( i ) )
164 {
165 Document d = r.document( i );
166
167 String lastModified = d.get( ArtifactInfo.LAST_MODIFIED );
168
169 if ( lastModified != null )
170 {
171 Date t = new Date( Long.parseLong( lastModified ) );
172
173
174 if ( t.after( timestamp ) )
175 {
176 chunk.add( i );
177 }
178 }
179 }
180 }
181
182 return chunk;
183 }
184
185 private void updateProperties( Properties properties, IndexPackingRequest request )
186 throws IOException
187 {
188 Set<Object> keys = new HashSet<>( properties.keySet() );
189 Map<Integer, String> dataMap = new TreeMap<>();
190
191
192 for ( Object key : keys )
193 {
194 String sKey = (String) key;
195
196 if ( sKey.startsWith( IndexingContext.INDEX_CHUNK_PREFIX ) )
197 {
198 Integer count = Integer.valueOf( sKey.substring( IndexingContext.INDEX_CHUNK_PREFIX.length() ) );
199 String value = properties.getProperty( sKey );
200
201 dataMap.put( count, value );
202 properties.remove( key );
203 }
204 }
205
206 String val = properties.getProperty( IndexingContext.INDEX_CHUNK_COUNTER );
207
208 int i = 0;
209
210 for ( Entry<Integer, String> entry : dataMap.entrySet() )
211 {
212
213 if ( i >= ( request.getMaxIndexChunks() - 1 ) )
214 {
215 break;
216 }
217
218 properties.put( IndexingContext.INDEX_CHUNK_PREFIX + ( entry.getKey() + 1 ), entry.getValue() );
219
220 i++;
221 }
222
223 int nextValue = Integer.parseInt( val ) + 1;
224
225
226 properties.put( IndexingContext.INDEX_CHUNK_PREFIX + "0", Integer.toString( nextValue ) );
227 properties.put( IndexingContext.INDEX_CHUNK_COUNTER, Integer.toString( nextValue ) );
228 }
229
230 private void cleanUpIncrementalChunks( IndexPackingRequest request, Properties properties )
231 {
232 File[] files = request.getTargetDir().listFiles( ( dir, name ) ->
233 {
234 String[] parts = name.split( "\\." );
235 return parts.length == 3 && parts[0].equals( IndexingContext.INDEX_FILE_PREFIX ) && parts[2].equals( "gz" );
236 } );
237
238 for ( File file : files )
239 {
240 String[] parts = file.getName().split( "\\." );
241
242 boolean found = false;
243 for ( Entry<Object, Object> entry : properties.entrySet() )
244 {
245 if ( entry.getKey().toString().startsWith(
246 IndexingContext.INDEX_CHUNK_PREFIX ) && entry.getValue().equals( parts[1] ) )
247 {
248 found = true;
249 break;
250 }
251 }
252
253 if ( !found )
254 {
255 file.delete();
256 }
257 }
258 }
259
260 private Date parse( String s )
261 {
262 try
263 {
264 SimpleDateFormat df = new SimpleDateFormat( IndexingContext.INDEX_TIME_FORMAT );
265 df.setTimeZone( TimeZone.getTimeZone( "GMT" ) );
266 return df.parse( s );
267 }
268 catch ( ParseException e )
269 {
270 return null;
271 }
272 }
273
274 private boolean canRetrieveAllChunks( Properties localProps, Properties remoteProps )
275 {
276
277 if ( localProps == null )
278 {
279 return false;
280 }
281
282 String localChainId = localProps.getProperty( IndexingContext.INDEX_CHAIN_ID );
283 String remoteChainId = remoteProps.getProperty( IndexingContext.INDEX_CHAIN_ID );
284
285
286 if ( StringUtils.isEmpty( localChainId ) || !localChainId.equals( remoteChainId ) )
287 {
288 return false;
289 }
290
291 String counterProp = localProps.getProperty( IndexingContext.INDEX_CHUNK_COUNTER );
292
293
294
295 if ( StringUtils.isEmpty( counterProp ) || !StringUtils.isNumeric( counterProp ) )
296 {
297 return false;
298 }
299
300 int currentLocalCounter = Integer.parseInt( counterProp );
301
302
303
304 for ( Object key : remoteProps.keySet() )
305 {
306 String sKey = (String) key;
307
308 if ( sKey.startsWith( IndexingContext.INDEX_CHUNK_PREFIX ) )
309 {
310 String value = remoteProps.getProperty( sKey );
311
312
313 if ( Integer.toString( currentLocalCounter ).equals( value ) || Integer.toString(
314 currentLocalCounter + 1 ).equals( value ) )
315 {
316 return true;
317 }
318 }
319 }
320
321 return false;
322 }
323 }