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