Coverage Report - org.apache.maven.index.updater.DefaultIndexUpdater
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultIndexUpdater
90 %
180/198
71 %
59/82
2,396
DefaultIndexUpdater$FileFetcher
77 %
7/9
N/A
2,396
DefaultIndexUpdater$IndexAdaptor
100 %
7/7
N/A
2,396
DefaultIndexUpdater$LocalCacheIndexAdaptor
84 %
39/46
50 %
5/10
2,396
DefaultIndexUpdater$LocalCacheIndexAdaptor$1
100 %
2/2
N/A
2,396
DefaultIndexUpdater$LocalIndexCacheFetcher
100 %
2/2
N/A
2,396
DefaultIndexUpdater$LuceneIndexAdaptor
93 %
15/16
50 %
1/2
2,396
 
 1  
 package org.apache.maven.index.updater;
 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.BufferedInputStream;
 23  
 import java.io.BufferedOutputStream;
 24  
 import java.io.BufferedReader;
 25  
 import java.io.File;
 26  
 import java.io.FileInputStream;
 27  
 import java.io.FileNotFoundException;
 28  
 import java.io.FileOutputStream;
 29  
 import java.io.IOException;
 30  
 import java.io.InputStream;
 31  
 import java.io.InputStreamReader;
 32  
 import java.io.OutputStream;
 33  
 import java.io.OutputStreamWriter;
 34  
 import java.io.Writer;
 35  
 import java.text.ParseException;
 36  
 import java.text.SimpleDateFormat;
 37  
 import java.util.ArrayList;
 38  
 import java.util.Date;
 39  
 import java.util.List;
 40  
 import java.util.Properties;
 41  
 import java.util.TimeZone;
 42  
 import java.util.zip.ZipEntry;
 43  
 import java.util.zip.ZipInputStream;
 44  
 
 45  
 import org.apache.lucene.document.Document;
 46  
 import org.apache.lucene.index.CorruptIndexException;
 47  
 import org.apache.lucene.index.IndexReader;
 48  
 import org.apache.lucene.index.IndexWriter;
 49  
 import org.apache.lucene.store.Directory;
 50  
 import org.apache.lucene.store.FSDirectory;
 51  
 import org.apache.lucene.store.IndexOutput;
 52  
 import org.apache.lucene.store.LockObtainFailedException;
 53  
 import org.apache.maven.index.context.DocumentFilter;
 54  
 import org.apache.maven.index.context.IndexUtils;
 55  
 import org.apache.maven.index.context.IndexingContext;
 56  
 import org.apache.maven.index.context.NexusAnalyzer;
 57  
 import org.apache.maven.index.context.NexusIndexWriter;
 58  
 import org.apache.maven.index.fs.Lock;
 59  
 import org.apache.maven.index.fs.Locker;
 60  
 import org.apache.maven.index.incremental.IncrementalHandler;
 61  
 import org.apache.maven.index.updater.IndexDataReader.IndexDataReadResult;
 62  
 import org.codehaus.plexus.component.annotations.Component;
 63  
 import org.codehaus.plexus.component.annotations.Requirement;
 64  
 import org.codehaus.plexus.logging.AbstractLogEnabled;
 65  
 import org.codehaus.plexus.util.FileUtils;
 66  
 import org.codehaus.plexus.util.IOUtil;
 67  
 import org.codehaus.plexus.util.io.RawInputStreamFacade;
 68  
 
 69  
 /**
 70  
  * A default index updater implementation
 71  
  * 
 72  
  * @author Jason van Zyl
 73  
  * @author Eugene Kuleshov
 74  
  */
 75  
 @Component( role = IndexUpdater.class )
 76  135
 public class DefaultIndexUpdater
 77  
     extends AbstractLogEnabled
 78  
     implements IndexUpdater
 79  
 {
 80  
 
 81  
     @Requirement( role = IncrementalHandler.class )
 82  
     IncrementalHandler incrementalHandler;
 83  
 
 84  
     @Requirement( role = IndexUpdateSideEffect.class )
 85  
     private List<IndexUpdateSideEffect> sideEffects;
 86  
 
 87  
     public DefaultIndexUpdater( final IncrementalHandler handler, final List<IndexUpdateSideEffect> mySideeffects )
 88  0
     {
 89  0
         incrementalHandler = handler;
 90  0
         sideEffects = mySideeffects;
 91  0
     }
 92  
 
 93  
     public DefaultIndexUpdater()
 94  22
     {
 95  
 
 96  22
     }
 97  
 
 98  
     public IndexUpdateResult fetchAndUpdateIndex( final IndexUpdateRequest updateRequest )
 99  
         throws IOException
 100  
     {
 101  27
         IndexUpdateResult result = new IndexUpdateResult();
 102  
 
 103  27
         IndexingContext context = updateRequest.getIndexingContext();
 104  
 
 105  27
         ResourceFetcher fetcher = null;
 106  
 
 107  27
         if ( !updateRequest.isOffline() )
 108  
         {
 109  25
             fetcher = updateRequest.getResourceFetcher();
 110  
 
 111  
             // If no resource fetcher passed in, use the wagon fetcher by default
 112  
             // and put back in request for future use
 113  25
             if ( fetcher == null )
 114  
             {
 115  0
                 throw new IOException( "Update of the index without provided ResourceFetcher is impossible." );
 116  
             }
 117  
 
 118  25
             fetcher.connect( context.getId(), context.getIndexUpdateUrl() );
 119  
         }
 120  
 
 121  27
         File cacheDir = updateRequest.getLocalIndexCacheDir();
 122  27
         Locker locker = updateRequest.getLocker();
 123  27
         Lock lock = locker != null && cacheDir != null ? locker.lock( cacheDir ) : null;
 124  
         try
 125  
         {
 126  27
             if ( cacheDir != null )
 127  
             {
 128  17
                 LocalCacheIndexAdaptor cache = new LocalCacheIndexAdaptor( cacheDir, result );
 129  
 
 130  17
                 if ( !updateRequest.isOffline() )
 131  
                 {
 132  15
                     cacheDir.mkdirs();
 133  
 
 134  
                     try
 135  
                     {
 136  15
                         fetchAndUpdateIndex( updateRequest, fetcher, cache );
 137  14
                         cache.commit();
 138  
                     }
 139  
                     finally
 140  
                     {
 141  15
                         fetcher.disconnect();
 142  14
                     }
 143  
                 }
 144  
 
 145  16
                 fetcher = cache.getFetcher();
 146  16
             }
 147  10
             else if ( updateRequest.isOffline() )
 148  
             {
 149  0
                 throw new IllegalArgumentException( "LocalIndexCacheDir can not be null in offline mode" );
 150  
             }
 151  
 
 152  
             try
 153  
             {
 154  26
                 if ( !updateRequest.isCacheOnly() )
 155  
                 {
 156  26
                     LuceneIndexAdaptor target = new LuceneIndexAdaptor( updateRequest );
 157  26
                     result.setTimestamp( fetchAndUpdateIndex( updateRequest, fetcher, target ) );
 158  26
                     target.commit();
 159  
                 }
 160  
             }
 161  
             finally
 162  
             {
 163  26
                 fetcher.disconnect();
 164  26
             }
 165  
         }
 166  
         finally
 167  
         {
 168  27
             if ( lock != null )
 169  
             {
 170  0
                 lock.release();
 171  
             }
 172  
         }
 173  
 
 174  26
         return result;
 175  
     }
 176  
 
 177  
     private Date loadIndexDirectory( final IndexUpdateRequest updateRequest, final ResourceFetcher fetcher,
 178  
                                      final boolean merge, final String remoteIndexFile )
 179  
         throws IOException
 180  
     {
 181  24
         File indexDir = File.createTempFile( remoteIndexFile, ".dir" );
 182  24
         indexDir.delete();
 183  24
         indexDir.mkdirs();
 184  
 
 185  24
         FSDirectory directory = FSDirectory.open( indexDir );
 186  
 
 187  24
         BufferedInputStream is = null;
 188  
 
 189  
         try
 190  
         {
 191  24
             is = new BufferedInputStream( fetcher.retrieve( remoteIndexFile ) );
 192  
 
 193  23
             Date timestamp = null;
 194  
 
 195  23
             if ( remoteIndexFile.endsWith( ".gz" ) )
 196  
             {
 197  22
                 timestamp = unpackIndexData( is, directory, //
 198  
                     updateRequest.getIndexingContext() );
 199  
             }
 200  
             else
 201  
             {
 202  
                 // legacy transfer format
 203  1
                 timestamp = unpackIndexArchive( is, directory, //
 204  
                     updateRequest.getIndexingContext() );
 205  
             }
 206  
 
 207  23
             if ( updateRequest.getDocumentFilter() != null )
 208  
             {
 209  1
                 filterDirectory( directory, updateRequest.getDocumentFilter() );
 210  
             }
 211  
 
 212  23
             if ( merge )
 213  
             {
 214  5
                 updateRequest.getIndexingContext().merge( directory );
 215  
             }
 216  
             else
 217  
             {
 218  18
                 updateRequest.getIndexingContext().replace( directory );
 219  
             }
 220  23
             if ( sideEffects != null && sideEffects.size() > 0 )
 221  
             {
 222  0
                 getLogger().info( IndexUpdateSideEffect.class.getName() + " extensions found: " + sideEffects.size() );
 223  0
                 for ( IndexUpdateSideEffect sideeffect : sideEffects )
 224  
                 {
 225  0
                     sideeffect.updateIndex( directory, updateRequest.getIndexingContext(), merge );
 226  
                 }
 227  
             }
 228  
 
 229  23
             return timestamp;
 230  
         }
 231  
         finally
 232  
         {
 233  24
             IOUtil.close( is );
 234  
 
 235  24
             if ( directory != null )
 236  
             {
 237  24
                 directory.close();
 238  
             }
 239  
 
 240  
             try
 241  
             {
 242  24
                 FileUtils.deleteDirectory( indexDir );
 243  
             }
 244  0
             catch ( IOException ex )
 245  
             {
 246  
                 // ignore
 247  48
             }
 248  
         }
 249  
     }
 250  
 
 251  
     /**
 252  
      * Unpack legacy index archive into a specified Lucene <code>Directory</code>
 253  
      * 
 254  
      * @param is a <code>ZipInputStream</code> with index data
 255  
      * @param directory Lucene <code>Directory</code> to unpack index data to
 256  
      * @return {@link Date} of the index update or null if it can't be read
 257  
      */
 258  
     public static Date unpackIndexArchive( final InputStream is, final Directory directory,
 259  
                                            final IndexingContext context )
 260  
         throws IOException
 261  
     {
 262  17
         File indexArchive = File.createTempFile( "nexus-index", "" );
 263  
 
 264  17
         File indexDir = new File( indexArchive.getAbsoluteFile().getParentFile(), indexArchive.getName() + ".dir" );
 265  
 
 266  17
         indexDir.mkdirs();
 267  
 
 268  17
         FSDirectory fdir = FSDirectory.open( indexDir );
 269  
 
 270  
         try
 271  
         {
 272  17
             unpackDirectory( fdir, is );
 273  17
             copyUpdatedDocuments( fdir, directory, context );
 274  
 
 275  17
             Date timestamp = IndexUtils.getTimestamp( fdir );
 276  17
             IndexUtils.updateTimestamp( directory, timestamp );
 277  17
             return timestamp;
 278  
         }
 279  
         finally
 280  
         {
 281  17
             IndexUtils.close( fdir );
 282  17
             indexArchive.delete();
 283  17
             IndexUtils.delete( indexDir );
 284  
         }
 285  
     }
 286  
 
 287  
     private static void unpackDirectory( final Directory directory, final InputStream is )
 288  
         throws IOException
 289  
     {
 290  17
         byte[] buf = new byte[4096];
 291  
 
 292  
         ZipEntry entry;
 293  
 
 294  17
         ZipInputStream zis = null;
 295  
 
 296  
         try
 297  
         {
 298  17
             zis = new ZipInputStream( is );
 299  
 
 300  160
             while ( ( entry = zis.getNextEntry() ) != null )
 301  
             {
 302  143
                 if ( entry.isDirectory() || entry.getName().indexOf( '/' ) > -1 )
 303  
                 {
 304  0
                     continue;
 305  
                 }
 306  
 
 307  143
                 IndexOutput io = directory.createOutput( entry.getName() );
 308  
                 try
 309  
                 {
 310  143
                     int n = 0;
 311  
 
 312  848
                     while ( ( n = zis.read( buf ) ) != -1 )
 313  
                     {
 314  705
                         io.writeBytes( buf, n );
 315  
                     }
 316  
                 }
 317  
                 finally
 318  
                 {
 319  143
                     IndexUtils.close( io );
 320  143
                 }
 321  143
             }
 322  
         }
 323  
         finally
 324  
         {
 325  17
             IndexUtils.close( zis );
 326  17
         }
 327  17
     }
 328  
 
 329  
     private static void copyUpdatedDocuments( final Directory sourcedir, final Directory targetdir,
 330  
                                               final IndexingContext context )
 331  
         throws CorruptIndexException, LockObtainFailedException, IOException
 332  
     {
 333  17
         IndexWriter w = null;
 334  17
         IndexReader r = null;
 335  
         try
 336  
         {
 337  17
             r = IndexReader.open( sourcedir );
 338  17
             w = new NexusIndexWriter( targetdir, new NexusAnalyzer(), true );
 339  
 
 340  631
             for ( int i = 0; i < r.maxDoc(); i++ )
 341  
             {
 342  614
                 if ( !r.isDeleted( i ) )
 343  
                 {
 344  614
                     w.addDocument( IndexUtils.updateDocument( r.document( i ), context ) );
 345  
                 }
 346  
             }
 347  
 
 348  17
             w.optimize();
 349  17
             w.commit();
 350  
         }
 351  
         finally
 352  
         {
 353  17
             IndexUtils.close( w );
 354  17
             IndexUtils.close( r );
 355  17
         }
 356  17
     }
 357  
 
 358  
     private static void filterDirectory( final Directory directory, final DocumentFilter filter )
 359  
         throws IOException
 360  
     {
 361  1
         IndexReader r = null;
 362  
         try
 363  
         {
 364  
             // explicitly RW reader needed
 365  1
             r = IndexReader.open( directory, false );
 366  
 
 367  1
             int numDocs = r.maxDoc();
 368  
 
 369  6
             for ( int i = 0; i < numDocs; i++ )
 370  
             {
 371  5
                 if ( r.isDeleted( i ) )
 372  
                 {
 373  0
                     continue;
 374  
                 }
 375  
 
 376  5
                 Document d = r.document( i );
 377  
 
 378  5
                 if ( !filter.accept( d ) )
 379  
                 {
 380  0
                     r.deleteDocument( i );
 381  
                 }
 382  
             }
 383  
         }
 384  
         finally
 385  
         {
 386  1
             IndexUtils.close( r );
 387  1
         }
 388  
 
 389  1
         IndexWriter w = null;
 390  
         try
 391  
         {
 392  
             // analyzer is unimportant, since we are not adding/searching to/on index, only reading/deleting
 393  1
             w = new NexusIndexWriter( directory, new NexusAnalyzer(), false );
 394  
 
 395  1
             w.optimize();
 396  
 
 397  1
             w.commit();
 398  
         }
 399  
         finally
 400  
         {
 401  1
             IndexUtils.close( w );
 402  1
         }
 403  1
     }
 404  
 
 405  
     private Properties loadIndexProperties( final File indexDirectoryFile, final String remoteIndexPropertiesName )
 406  
     {
 407  30
         File indexProperties = new File( indexDirectoryFile, remoteIndexPropertiesName );
 408  
 
 409  30
         FileInputStream fis = null;
 410  
 
 411  
         try
 412  
         {
 413  30
             Properties properties = new Properties();
 414  
 
 415  30
             fis = new FileInputStream( indexProperties );
 416  
 
 417  13
             properties.load( fis );
 418  
 
 419  13
             return properties;
 420  
         }
 421  17
         catch ( IOException e )
 422  
         {
 423  17
             getLogger().debug( "Unable to read remote properties stored locally", e );
 424  
         }
 425  
         finally
 426  
         {
 427  30
             IOUtil.close( fis );
 428  17
         }
 429  
 
 430  17
         return null;
 431  
     }
 432  
 
 433  
     private void storeIndexProperties( final File dir, final String indexPropertiesName, final Properties properties )
 434  
         throws IOException
 435  
     {
 436  40
         File file = new File( dir, indexPropertiesName );
 437  
 
 438  40
         if ( properties != null )
 439  
         {
 440  40
             OutputStream os = new BufferedOutputStream( new FileOutputStream( file ) );
 441  
             try
 442  
             {
 443  40
                 properties.store( os, null );
 444  
             }
 445  
             finally
 446  
             {
 447  40
                 IOUtil.close( os );
 448  40
             }
 449  40
         }
 450  
         else
 451  
         {
 452  0
             file.delete();
 453  
         }
 454  40
     }
 455  
 
 456  
     private Properties downloadIndexProperties( final ResourceFetcher fetcher )
 457  
         throws IOException
 458  
     {
 459  41
         InputStream fis = fetcher.retrieve( IndexingContext.INDEX_REMOTE_PROPERTIES_FILE );
 460  
 
 461  
         try
 462  
         {
 463  41
             Properties properties = new Properties();
 464  
 
 465  41
             properties.load( fis );
 466  
 
 467  41
             return properties;
 468  
         }
 469  
         finally
 470  
         {
 471  41
             IOUtil.close( fis );
 472  
         }
 473  
     }
 474  
 
 475  
     public Date getTimestamp( final Properties properties, final String key )
 476  
     {
 477  43
         String indexTimestamp = properties.getProperty( key );
 478  
 
 479  43
         if ( indexTimestamp != null )
 480  
         {
 481  
             try
 482  
             {
 483  41
                 SimpleDateFormat df = new SimpleDateFormat( IndexingContext.INDEX_TIME_FORMAT );
 484  41
                 df.setTimeZone( TimeZone.getTimeZone( "GMT" ) );
 485  41
                 return df.parse( indexTimestamp );
 486  
             }
 487  0
             catch ( ParseException ex )
 488  
             {
 489  
             }
 490  
         }
 491  2
         return null;
 492  
     }
 493  
 
 494  
     /**
 495  
      * Unpack index data using specified Lucene Index writer
 496  
      * 
 497  
      * @param is an input stream to unpack index data from
 498  
      * @param w a writer to save index data
 499  
      * @param ics a collection of index creators for updating unpacked documents.
 500  
      */
 501  
     public static Date unpackIndexData( final InputStream is, final Directory d, final IndexingContext context )
 502  
         throws IOException
 503  
     {
 504  35
         NexusIndexWriter w = new NexusIndexWriter( d, new NexusAnalyzer(), true );
 505  
         try
 506  
         {
 507  35
             IndexDataReader dr = new IndexDataReader( is );
 508  
 
 509  35
             IndexDataReadResult result = dr.readIndex( w, context );
 510  
 
 511  35
             return result.getTimestamp();
 512  
         }
 513  
         finally
 514  
         {
 515  35
             IndexUtils.close( w );
 516  
         }
 517  
     }
 518  
 
 519  
     /**
 520  
      * Filesystem-based ResourceFetcher implementation
 521  
      */
 522  
     public static class FileFetcher
 523  
         implements ResourceFetcher
 524  
     {
 525  
         private final File basedir;
 526  
 
 527  
         public FileFetcher( File basedir )
 528  32
         {
 529  32
             this.basedir = basedir;
 530  32
         }
 531  
 
 532  
         public void connect( String id, String url )
 533  
             throws IOException
 534  
         {
 535  
             // don't need to do anything
 536  15
         }
 537  
 
 538  
         public void disconnect()
 539  
             throws IOException
 540  
         {
 541  
             // don't need to do anything
 542  31
         }
 543  
 
 544  
         public void retrieve( String name, File targetFile )
 545  
             throws IOException, FileNotFoundException
 546  
         {
 547  0
             FileUtils.copyFile( getFile( name ), targetFile );
 548  
 
 549  0
         }
 550  
 
 551  
         public InputStream retrieve( String name )
 552  
             throws IOException, FileNotFoundException
 553  
         {
 554  56
             return new FileInputStream( getFile( name ) );
 555  
         }
 556  
 
 557  
         private File getFile( String name )
 558  
         {
 559  56
             return new File( basedir, name );
 560  
         }
 561  
 
 562  
     }
 563  
 
 564  
     private abstract class IndexAdaptor
 565  
     {
 566  
         protected final File dir;
 567  
 
 568  
         protected Properties properties;
 569  
 
 570  
         protected IndexAdaptor( File dir )
 571  43
         {
 572  43
             this.dir = dir;
 573  43
         }
 574  
 
 575  
         public abstract Properties getProperties();
 576  
 
 577  
         public abstract void storeProperties()
 578  
             throws IOException;
 579  
 
 580  
         public abstract void addIndexChunk( ResourceFetcher source, String filename )
 581  
             throws IOException;
 582  
 
 583  
         public abstract Date setIndexFile( ResourceFetcher source, String string )
 584  
             throws IOException;
 585  
 
 586  
         public Properties setProperties( ResourceFetcher source )
 587  
             throws IOException
 588  
         {
 589  41
             this.properties = downloadIndexProperties( source );
 590  41
             return properties;
 591  
         }
 592  
 
 593  
         public abstract Date getTimestamp();
 594  
 
 595  
         public void commit()
 596  
             throws IOException
 597  
         {
 598  40
             storeProperties();
 599  40
         }
 600  
     }
 601  
 
 602  
     private class LuceneIndexAdaptor
 603  
         extends IndexAdaptor
 604  
     {
 605  
         private final IndexUpdateRequest updateRequest;
 606  
 
 607  
         public LuceneIndexAdaptor( IndexUpdateRequest updateRequest )
 608  26
         {
 609  26
             super( updateRequest.getIndexingContext().getIndexDirectoryFile() );
 610  26
             this.updateRequest = updateRequest;
 611  26
         }
 612  
 
 613  
         public Properties getProperties()
 614  
         {
 615  18
             if ( properties == null )
 616  
             {
 617  18
                 properties = loadIndexProperties( dir, IndexingContext.INDEX_UPDATER_PROPERTIES_FILE );
 618  
             }
 619  18
             return properties;
 620  
         }
 621  
 
 622  
         public void storeProperties()
 623  
             throws IOException
 624  
         {
 625  26
             storeIndexProperties( dir, IndexingContext.INDEX_UPDATER_PROPERTIES_FILE, properties );
 626  26
         }
 627  
 
 628  
         public Date getTimestamp()
 629  
         {
 630  0
             return updateRequest.getIndexingContext().getTimestamp();
 631  
         }
 632  
 
 633  
         public void addIndexChunk( ResourceFetcher source, String filename )
 634  
             throws IOException
 635  
         {
 636  5
             loadIndexDirectory( updateRequest, source, true, filename );
 637  5
         }
 638  
 
 639  
         public Date setIndexFile( ResourceFetcher source, String filename )
 640  
             throws IOException
 641  
         {
 642  19
             return loadIndexDirectory( updateRequest, source, false, filename );
 643  
         }
 644  
 
 645  
         public void commit()
 646  
             throws IOException
 647  
         {
 648  26
             super.commit();
 649  
 
 650  26
             updateRequest.getIndexingContext().commit();
 651  26
         }
 652  
 
 653  
     }
 654  
 
 655  
     private class LocalCacheIndexAdaptor
 656  
         extends IndexAdaptor
 657  
     {
 658  
         private static final String CHUNKS_FILENAME = "chunks.lst";
 659  
 
 660  
         private static final String CHUNKS_FILE_ENCODING = "UTF-8";
 661  
 
 662  
         private final IndexUpdateResult result;
 663  
 
 664  17
         private final ArrayList<String> newChunks = new ArrayList<String>();
 665  
 
 666  
         public LocalCacheIndexAdaptor( File dir, IndexUpdateResult result )
 667  17
         {
 668  17
             super( dir );
 669  17
             this.result = result;
 670  17
         }
 671  
 
 672  
         public Properties getProperties()
 673  
         {
 674  12
             if ( properties == null )
 675  
             {
 676  12
                 properties = loadIndexProperties( dir, IndexingContext.INDEX_REMOTE_PROPERTIES_FILE );
 677  
             }
 678  12
             return properties;
 679  
         }
 680  
 
 681  
         public void storeProperties()
 682  
             throws IOException
 683  
         {
 684  14
             storeIndexProperties( dir, IndexingContext.INDEX_REMOTE_PROPERTIES_FILE, properties );
 685  14
         }
 686  
 
 687  
         public Date getTimestamp()
 688  
         {
 689  0
             Properties properties = getProperties();
 690  0
             if ( properties == null )
 691  
             {
 692  0
                 return null;
 693  
             }
 694  
 
 695  0
             Date timestamp = DefaultIndexUpdater.this.getTimestamp( properties, IndexingContext.INDEX_TIMESTAMP );
 696  
 
 697  0
             if ( timestamp == null )
 698  
             {
 699  0
                 timestamp = DefaultIndexUpdater.this.getTimestamp( properties, IndexingContext.INDEX_LEGACY_TIMESTAMP );
 700  
             }
 701  
 
 702  0
             return timestamp;
 703  
         }
 704  
 
 705  
         public void addIndexChunk( ResourceFetcher source, String filename )
 706  
             throws IOException
 707  
         {
 708  2
             File chunk = new File( dir, filename );
 709  2
             FileUtils.copyStreamToFile( new RawInputStreamFacade( source.retrieve( filename ) ), chunk );
 710  2
             newChunks.add( filename );
 711  2
         }
 712  
 
 713  
         public Date setIndexFile( ResourceFetcher source, String filename )
 714  
             throws IOException
 715  
         {
 716  11
             cleanCacheDirectory( dir );
 717  
 
 718  11
             result.setFullUpdate( true );
 719  
 
 720  11
             File target = new File( dir, filename );
 721  11
             FileUtils.copyStreamToFile( new RawInputStreamFacade( source.retrieve( filename ) ), target );
 722  
 
 723  9
             return null;
 724  
         }
 725  
 
 726  
         @Override
 727  
         public void commit()
 728  
             throws IOException
 729  
         {
 730  14
             File chunksFile = new File( dir, CHUNKS_FILENAME );
 731  14
             BufferedOutputStream os = new BufferedOutputStream( new FileOutputStream( chunksFile, true ) );
 732  14
             Writer w = new OutputStreamWriter( os, CHUNKS_FILE_ENCODING );
 733  
             try
 734  
             {
 735  14
                 for ( String filename : newChunks )
 736  
                 {
 737  2
                     w.write( filename + "\n" );
 738  
                 }
 739  14
                 w.flush();
 740  
             }
 741  
             finally
 742  
             {
 743  14
                 IOUtil.close( w );
 744  14
                 IOUtil.close( os );
 745  14
             }
 746  14
             super.commit();
 747  14
         }
 748  
 
 749  
         public List<String> getChunks()
 750  
             throws IOException
 751  
         {
 752  11
             ArrayList<String> chunks = new ArrayList<String>();
 753  
 
 754  11
             File chunksFile = new File( dir, CHUNKS_FILENAME );
 755  11
             BufferedReader r =
 756  
                 new BufferedReader( new InputStreamReader( new FileInputStream( chunksFile ), CHUNKS_FILE_ENCODING ) );
 757  
             try
 758  
             {
 759  
                 String str;
 760  13
                 while ( ( str = r.readLine() ) != null )
 761  
                 {
 762  2
                     chunks.add( str );
 763  
                 }
 764  
             }
 765  
             finally
 766  
             {
 767  11
                 IOUtil.close( r );
 768  11
             }
 769  11
             return chunks;
 770  
         }
 771  
 
 772  
         public ResourceFetcher getFetcher()
 773  
         {
 774  16
             return new LocalIndexCacheFetcher( dir )
 775  16
             {
 776  
                 @Override
 777  
                 public List<String> getChunks()
 778  
                     throws IOException
 779  
                 {
 780  11
                     return LocalCacheIndexAdaptor.this.getChunks();
 781  
                 }
 782  
             };
 783  
         }
 784  
     }
 785  
 
 786  
     abstract static class LocalIndexCacheFetcher
 787  
         extends FileFetcher
 788  
     {
 789  
         public LocalIndexCacheFetcher( File basedir )
 790  
         {
 791  16
             super( basedir );
 792  16
         }
 793  
 
 794  
         public abstract List<String> getChunks()
 795  
             throws IOException;
 796  
     }
 797  
 
 798  
     private Date fetchAndUpdateIndex( final IndexUpdateRequest updateRequest, ResourceFetcher source,
 799  
                                       IndexAdaptor target )
 800  
         throws IOException
 801  
     {
 802  41
         if ( !updateRequest.isForceFullUpdate() )
 803  
         {
 804  30
             Properties localProperties = target.getProperties();
 805  30
             Date localTimestamp = null;
 806  
 
 807  30
             if ( localProperties != null )
 808  
             {
 809  13
                 localTimestamp = getTimestamp( localProperties, IndexingContext.INDEX_TIMESTAMP );
 810  
             }
 811  
 
 812  
             // this will download and store properties in the target, so next run
 813  
             // target.getProperties() will retrieve it
 814  30
             Properties remoteProperties = target.setProperties( source );
 815  
 
 816  30
             Date updateTimestamp = getTimestamp( remoteProperties, IndexingContext.INDEX_TIMESTAMP );
 817  
 
 818  
             // If new timestamp is missing, dont bother checking incremental, we have an old file
 819  30
             if ( updateTimestamp != null )
 820  
             {
 821  30
                 List<String> filenames =
 822  
                     incrementalHandler.loadRemoteIncrementalUpdates( updateRequest, localProperties, remoteProperties );
 823  
 
 824  
                 // if we have some incremental files, merge them in
 825  30
                 if ( filenames != null )
 826  
                 {
 827  7
                     for ( String filename : filenames )
 828  
                     {
 829  5
                         target.addIndexChunk( source, filename );
 830  
                     }
 831  
 
 832  7
                     return updateTimestamp;
 833  
                 }
 834  23
             }
 835  
             else
 836  
             {
 837  0
                 updateTimestamp = getTimestamp( remoteProperties, IndexingContext.INDEX_LEGACY_TIMESTAMP );
 838  
             }
 839  
 
 840  
             // fallback to timestamp comparison, but try with one coming from local properties, and if not possible (is
 841  
             // null)
 842  
             // fallback to context timestamp
 843  23
             if ( localTimestamp != null )
 844  
             {
 845  
                 // if we have localTimestamp
 846  
                 // if incremental can't be done for whatever reason, simply use old logic of
 847  
                 // checking the timestamp, if the same, nothing to do
 848  6
                 if ( updateTimestamp != null && localTimestamp != null && !updateTimestamp.after( localTimestamp ) )
 849  
                 {
 850  6
                     return null; // index is up to date
 851  
                 }
 852  
             }
 853  17
         }
 854  
         else
 855  
         {
 856  
             // create index properties during forced full index download
 857  11
             target.setProperties( source );
 858  
         }
 859  
 
 860  
         try
 861  
         {
 862  28
             Date timestamp = target.setIndexFile( source, IndexingContext.INDEX_FILE_PREFIX + ".gz" );
 863  26
             if ( source instanceof LocalIndexCacheFetcher )
 864  
             {
 865  
                 // local cache has inverse organization compared to remote indexes,
 866  
                 // i.e. initial index file and delta chunks to apply on top of it
 867  11
                 for ( String filename : ( (LocalIndexCacheFetcher) source ).getChunks() )
 868  
                 {
 869  2
                     target.addIndexChunk( source, filename );
 870  
                 }
 871  
             }
 872  26
             return timestamp;
 873  
         }
 874  2
         catch ( IOException ex )
 875  
         {
 876  
             // try to look for legacy index transfer format
 877  
             try
 878  
             {
 879  2
                 return target.setIndexFile( source, IndexingContext.INDEX_FILE_PREFIX + ".zip" );
 880  
             }
 881  1
             catch ( IOException ex2 )
 882  
             {
 883  1
                 getLogger().error( "Fallback to *.zip also failed: " + ex2 ); // do not bother with stack trace
 884  
                 
 885  1
                 throw ex; // original exception more likely to be interesting
 886  
             }
 887  
         }
 888  
     }
 889  
 
 890  
     /**
 891  
      * Cleans specified cache directory. If present, Locker.LOCK_FILE will not be deleted.
 892  
      */
 893  
     protected void cleanCacheDirectory( File dir )
 894  
         throws IOException
 895  
     {
 896  11
         File[] members = dir.listFiles();
 897  11
         if ( members == null )
 898  
         {
 899  0
             return;
 900  
         }
 901  
 
 902  21
         for ( File member : members )
 903  
         {
 904  10
             if ( !Locker.LOCK_FILE.equals( member.getName() ) )
 905  
             {
 906  9
                 FileUtils.forceDelete( member );
 907  
             }
 908  
         }
 909  11
     }
 910  
 
 911  
 }