View Javadoc

1   package org.apache.maven.index.incremental;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0    
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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          // Get the list of document ids that have been added since the last time
66          // the index ran
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          // if no documents, then we don't need to do anything, no changes
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          // If we have local properties, will parse and see what we need to download
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              // Start with the next one
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     // Note Toni:
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                         // Only add documents that were added after the last time we indexed
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         // First go through and retrieve all keys and their values
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         // Next put the items back in w/ proper keys
207         for ( Entry<Integer, String> entry : dataMap.entrySet() )
208         {
209             // make sure to end if we reach limit, 0 based
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         // Now put the new one in, and update the counter
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         // no localprops, can't retrieve chunks
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         // If no chain id, or not the same, do whole download
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         // no counter, cant retrieve chunks
302         // not a number, cant retrieve chunks
303         if ( StringUtils.isEmpty( counterProp ) || !StringUtils.isNumeric( counterProp ) )
304         {
305             return false;
306         }
307 
308         int currentLocalCounter = Integer.parseInt( counterProp );
309 
310         // check remote props for existence of next chunk after local
311         // if we find it, then we are ok to retrieve the rest of the chunks
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                 // If we have the current counter, or the next counter, we are good to go
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 }