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.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          // Get the list of document ids that have been added since the last time
77          // the index ran
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          // if no documents, then we don't need to do anything, no changes
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          // If we have local properties, will parse and see what we need to download
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             // Start with the next one
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                     // Only add documents that were added after the last time we indexed
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         // First go through and retrieve all keys and their values
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         // Next put the items back in w/ proper keys
210         for ( Entry<Integer, String> entry : dataMap.entrySet() )
211         {
212             // make sure to end if we reach limit, 0 based
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         // Now put the new one in, and update the counter
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         // no localprops, can't retrieve chunks
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         // If no chain id, or not the same, do whole download
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         // no counter, cant retrieve chunks
294         // not a number, cant retrieve chunks
295         if ( StringUtils.isEmpty( counterProp ) || !StringUtils.isNumeric( counterProp ) )
296         {
297             return false;
298         }
299 
300         int currentLocalCounter = Integer.parseInt( counterProp );
301 
302         // check remote props for existence of next chunk after local
303         // if we find it, then we are ok to retrieve the rest of the chunks
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                 // If we have the current counter, or the next counter, we are good to go
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 }