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