001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.providers; 020 021import java.io.BufferedInputStream; 022import java.io.BufferedOutputStream; 023import java.io.File; 024import java.io.FileInputStream; 025import java.io.FileOutputStream; 026import java.io.IOException; 027import java.io.InputStream; 028import java.io.OutputStream; 029import java.util.ArrayList; 030import java.util.Collection; 031import java.util.Date; 032import java.util.Iterator; 033import java.util.List; 034import java.util.Properties; 035 036import org.apache.commons.io.IOUtils; 037import org.apache.log4j.Logger; 038import org.apache.wiki.InternalWikiException; 039import org.apache.wiki.WikiEngine; 040import org.apache.wiki.WikiPage; 041import org.apache.wiki.WikiProvider; 042import org.apache.wiki.api.exceptions.NoRequiredPropertyException; 043import org.apache.wiki.api.exceptions.ProviderException; 044import org.apache.wiki.util.FileUtil; 045 046/** 047 * Provides a simple directory based repository for Wiki pages. 048 * Pages are held in a directory structure: 049 * <PRE> 050 * Main.txt 051 * Foobar.txt 052 * OLD/ 053 * Main/ 054 * 1.txt 055 * 2.txt 056 * page.properties 057 * Foobar/ 058 * page.properties 059 * </PRE> 060 * 061 * In this case, "Main" has three versions, and "Foobar" just one version. 062 * <P> 063 * The properties file contains the necessary metainformation (such as author) 064 * information of the page. DO NOT MESS WITH IT! 065 * 066 * <P> 067 * All files have ".txt" appended to make life easier for those 068 * who insist on using Windows or other software which makes assumptions 069 * on the files contents based on its name. 070 * 071 */ 072public class VersioningFileProvider 073 extends AbstractFileProvider 074{ 075 private static final Logger log = Logger.getLogger(VersioningFileProvider.class); 076 077 /** Name of the directory where the old versions are stored. */ 078 public static final String PAGEDIR = "OLD"; 079 080 /** Name of the property file which stores the metadata. */ 081 public static final String PROPERTYFILE = "page.properties"; 082 083 private CachedProperties m_cachedProperties; 084 085 /** 086 * {@inheritDoc} 087 */ 088 @Override 089 public void initialize( WikiEngine engine, Properties properties ) 090 throws NoRequiredPropertyException, 091 IOException 092 { 093 super.initialize( engine, properties ); 094 // some additional sanity checks : 095 File oldpages = new File(getPageDirectory(), PAGEDIR); 096 if (!oldpages.exists()) 097 { 098 if (!oldpages.mkdirs()) 099 { 100 throw new IOException("Failed to create page version directory " + oldpages.getAbsolutePath()); 101 } 102 } 103 else 104 { 105 if (!oldpages.isDirectory()) 106 { 107 throw new IOException("Page version directory is not a directory: " + oldpages.getAbsolutePath()); 108 } 109 if (!oldpages.canWrite()) 110 { 111 throw new IOException("Page version directory is not writable: " + oldpages.getAbsolutePath()); 112 } 113 } 114 log.info("Using directory " + oldpages.getAbsolutePath() + " for storing old versions of pages"); 115 } 116 117 /** 118 * Returns the directory where the old versions of the pages 119 * are being kept. 120 */ 121 private File findOldPageDir( String page ) 122 { 123 if( page == null ) 124 { 125 throw new InternalWikiException("Page may NOT be null in the provider!"); 126 } 127 128 File oldpages = new File( getPageDirectory(), PAGEDIR ); 129 130 return new File( oldpages, mangleName(page) ); 131 } 132 133 /** 134 * Goes through the repository and decides which version is 135 * the newest one in that directory. 136 * 137 * @return Latest version number in the repository, or -1, if 138 * there is no page in the repository. 139 */ 140 141 // FIXME: This is relatively slow. 142 /* 143 private int findLatestVersion( String page ) 144 { 145 File pageDir = findOldPageDir( page ); 146 147 String[] pages = pageDir.list( new WikiFileFilter() ); 148 149 if( pages == null ) 150 { 151 return -1; // No such thing found. 152 } 153 154 int version = -1; 155 156 for( int i = 0; i < pages.length; i++ ) 157 { 158 int cutpoint = pages[i].indexOf( '.' ); 159 if( cutpoint > 0 ) 160 { 161 String pageNum = pages[i].substring( 0, cutpoint ); 162 163 try 164 { 165 int res = Integer.parseInt( pageNum ); 166 167 if( res > version ) 168 { 169 version = res; 170 } 171 } 172 catch( NumberFormatException e ) {} // It's okay to skip these. 173 } 174 } 175 176 return version; 177 } 178*/ 179 private int findLatestVersion( String page ) 180 { 181 int version = -1; 182 183 try 184 { 185 Properties props = getPageProperties( page ); 186 187 for( Iterator<Object> i = props.keySet().iterator(); i.hasNext(); ) 188 { 189 String key = (String)i.next(); 190 191 if( key.endsWith(".author") ) 192 { 193 int cutpoint = key.indexOf('.'); 194 if( cutpoint > 0 ) 195 { 196 String pageNum = key.substring(0,cutpoint); 197 198 try 199 { 200 int res = Integer.parseInt( pageNum ); 201 202 if( res > version ) 203 { 204 version = res; 205 } 206 } 207 catch( NumberFormatException e ) {} // It's okay to skip these. 208 } 209 } 210 } 211 } 212 catch( IOException e ) 213 { 214 log.error("Unable to figure out latest version - dying...",e); 215 } 216 217 return version; 218 } 219 220 /** 221 * Reads page properties from the file system. 222 */ 223 private Properties getPageProperties( String page ) 224 throws IOException 225 { 226 File propertyFile = new File( findOldPageDir(page), PROPERTYFILE ); 227 228 if( propertyFile.exists() ) 229 { 230 long lastModified = propertyFile.lastModified(); 231 232 // 233 // The profiler showed that when calling the history of a page the propertyfile 234 // was read just as much times as there were versions of that file. The loading 235 // of a propertyfile is a cpu-intensive job. So now hold on to the last propertyfile 236 // read because the next method will with a high probability ask for the same propertyfile. 237 // The time it took to show a historypage with 267 versions dropped with 300%. 238 // 239 240 CachedProperties cp = m_cachedProperties; 241 242 if( cp != null 243 && cp.m_page.equals(page) 244 && cp.m_lastModified == lastModified) 245 { 246 return cp.m_props; 247 } 248 249 InputStream in = null; 250 251 try 252 { 253 in = new BufferedInputStream(new FileInputStream( propertyFile )); 254 255 Properties props = new Properties(); 256 257 props.load(in); 258 259 cp = new CachedProperties( page, props, lastModified ); 260 m_cachedProperties = cp; // Atomic 261 262 return props; 263 } 264 finally 265 { 266 IOUtils.closeQuietly( in ); 267 } 268 } 269 270 return new Properties(); // Returns an empty object 271 } 272 273 /** 274 * Writes the page properties back to the file system. 275 * Note that it WILL overwrite any previous properties. 276 */ 277 private void putPageProperties( String page, Properties properties ) 278 throws IOException 279 { 280 File propertyFile = new File( findOldPageDir(page), PROPERTYFILE ); 281 OutputStream out = null; 282 283 try 284 { 285 out = new FileOutputStream( propertyFile ); 286 287 properties.store( out, " JSPWiki page properties for "+page+". DO NOT MODIFY!" ); 288 } 289 finally 290 { 291 IOUtils.closeQuietly( out ); 292 } 293 294 // The profiler showed the probability was very high that when 295 // calling for the history of a page the propertyfile would be 296 // read as much times as there were versions of that file. 297 // It is statistically likely the propertyfile will be examined 298 // many times before it is updated. 299 CachedProperties cp = 300 new CachedProperties( page, properties, propertyFile.lastModified() ); 301 m_cachedProperties = cp; // Atomic 302 } 303 304 /** 305 * Figures out the real version number of the page and also checks 306 * for its existence. 307 * 308 * @throws NoSuchVersionException if there is no such version. 309 */ 310 private int realVersion( String page, int requestedVersion ) 311 throws NoSuchVersionException, 312 ProviderException 313 { 314 // 315 // Quickly check for the most common case. 316 // 317 if( requestedVersion == WikiProvider.LATEST_VERSION ) 318 { 319 return -1; 320 } 321 322 int latest = findLatestVersion(page); 323 324 if( requestedVersion == latest || 325 (requestedVersion == 1 && latest == -1 ) ) 326 { 327 return -1; 328 } 329 else if( requestedVersion <= 0 || requestedVersion > latest ) 330 { 331 throw new NoSuchVersionException("Requested version "+requestedVersion+", but latest is "+latest ); 332 } 333 334 return requestedVersion; 335 } 336 337 /** 338 * {@inheritDoc} 339 */ 340 @Override 341 public synchronized String getPageText( String page, int version ) 342 throws ProviderException 343 { 344 File dir = findOldPageDir( page ); 345 346 version = realVersion( page, version ); 347 if( version == -1 ) 348 { 349 // We can let the FileSystemProvider take care 350 // of these requests. 351 return super.getPageText( page, WikiPageProvider.LATEST_VERSION ); 352 } 353 354 File pageFile = new File( dir, ""+version+FILE_EXT ); 355 356 if( !pageFile.exists() ) 357 throw new NoSuchVersionException("Version "+version+"does not exist."); 358 359 return readFile( pageFile ); 360 } 361 362 363 // FIXME: Should this really be here? 364 private String readFile( File pagedata ) 365 throws ProviderException 366 { 367 String result = null; 368 InputStream in = null; 369 370 if( pagedata.exists() ) 371 { 372 if( pagedata.canRead() ) 373 { 374 try 375 { 376 in = new FileInputStream( pagedata ); 377 result = FileUtil.readContents( in, m_encoding ); 378 } 379 catch( IOException e ) 380 { 381 log.error("Failed to read", e); 382 throw new ProviderException("I/O error: "+e.getMessage()); 383 } 384 finally 385 { 386 IOUtils.closeQuietly( in ); 387 } 388 } 389 else 390 { 391 log.warn("Failed to read page from '"+pagedata.getAbsolutePath()+"', possibly a permissions problem"); 392 throw new ProviderException("I cannot read the requested page."); 393 } 394 } 395 else 396 { 397 // This is okay. 398 // FIXME: is it? 399 log.info("New page"); 400 } 401 402 return result; 403 } 404 405 // FIXME: This method has no rollback whatsoever. 406 407 /* 408 This is how the page directory should look like: 409 410 version pagedir olddir 411 none empty empty 412 1 Main.txt (1) empty 413 2 Main.txt (2) 1.txt 414 3 Main.txt (3) 1.txt, 2.txt 415 */ 416 /** 417 * {@inheritDoc} 418 */ 419 @Override 420 public synchronized void putPageText( WikiPage page, String text ) 421 throws ProviderException 422 { 423 // 424 // This is a bit complicated. We'll first need to 425 // copy the old file to be the newest file. 426 // 427 428 File pageDir = findOldPageDir( page.getName() ); 429 430 if( !pageDir.exists() ) 431 { 432 pageDir.mkdirs(); 433 } 434 435 int latest = findLatestVersion( page.getName() ); 436 437 try 438 { 439 // 440 // Copy old data to safety, if one exists. 441 // 442 443 File oldFile = findPage( page.getName() ); 444 445 // Figure out which version should the old page be? 446 // Numbers should always start at 1. 447 // "most recent" = -1 ==> 1 448 // "first" = 1 ==> 2 449 450 int versionNumber = (latest > 0) ? latest : 1; 451 boolean firstUpdate = (versionNumber == 1); 452 453 if( oldFile != null && oldFile.exists() ) 454 { 455 InputStream in = null; 456 OutputStream out = null; 457 458 try 459 { 460 in = new BufferedInputStream( new FileInputStream( oldFile ) ); 461 File pageFile = new File( pageDir, Integer.toString( versionNumber )+FILE_EXT ); 462 out = new BufferedOutputStream( new FileOutputStream( pageFile ) ); 463 464 FileUtil.copyContents( in, out ); 465 466 // 467 // We need also to set the date, since we rely on this. 468 // 469 pageFile.setLastModified( oldFile.lastModified() ); 470 471 // 472 // Kludge to make the property code to work properly. 473 // 474 versionNumber++; 475 } 476 finally 477 { 478 IOUtils.closeQuietly( out ); 479 IOUtils.closeQuietly( in ); 480 } 481 } 482 483 // 484 // Let superclass handler writing data to a new version. 485 // 486 487 super.putPageText( page, text ); 488 489 // 490 // Finally, write page version data. 491 // 492 493 // FIXME: No rollback available. 494 Properties props = getPageProperties( page.getName() ); 495 496 String authorFirst = null; 497 // if the following file exists, we are NOT migrating from FileSystemProvider 498 File pagePropFile = new File(getPageDirectory() + File.separator + PAGEDIR + File.separator + mangleName(page.getName()) + File.separator + "page" + FileSystemProvider.PROP_EXT); 499 if ( firstUpdate && ! pagePropFile.exists()) 500 { 501 // we might not yet have a versioned author because the 502 // old page was last maintained by FileSystemProvider 503 Properties props2 = getHeritagePageProperties( page.getName() ); 504 505 // remember the simulated original author (or something) 506 // in the new properties 507 authorFirst = props2.getProperty( "1.author", "unknown" ); 508 props.setProperty( "1.author", authorFirst ); 509 } 510 511 String newAuthor = page.getAuthor(); 512 if ( newAuthor == null ) 513 { 514 newAuthor = ( authorFirst != null ) ? authorFirst : "unknown"; 515 } 516 page.setAuthor(newAuthor); 517 props.setProperty( versionNumber + ".author", newAuthor ); 518 519 String changeNote = (String) page.getAttribute(WikiPage.CHANGENOTE); 520 if( changeNote != null ) 521 { 522 props.setProperty( versionNumber+".changenote", changeNote ); 523 } 524 525 // Get additional custom properties from page and add to props 526 getCustomProperties(page, props); 527 528 putPageProperties( page.getName(), props ); 529 } 530 catch( IOException e ) 531 { 532 log.error( "Saving failed", e ); 533 throw new ProviderException("Could not save page text: "+e.getMessage()); 534 } 535 } 536 537 /** 538 * {@inheritDoc} 539 */ 540 @Override 541 public WikiPage getPageInfo( String page, int version ) 542 throws ProviderException 543 { 544 int latest = findLatestVersion(page); 545 int realVersion; 546 547 WikiPage p = null; 548 549 if( version == WikiPageProvider.LATEST_VERSION || 550 version == latest || 551 (version == 1 && latest == -1) ) 552 { 553 // 554 // Yes, we need to talk to the top level directory 555 // to get this version. 556 // 557 // I am listening to Press Play On Tape's guitar version of 558 // the good old C64 "Wizardry" -tune at this moment. 559 // Oh, the memories... 560 // 561 realVersion = (latest >= 0) ? latest : 1; 562 563 p = super.getPageInfo( page, WikiPageProvider.LATEST_VERSION ); 564 565 if( p != null ) 566 { 567 p.setVersion( realVersion ); 568 } 569 } 570 else 571 { 572 // 573 // The file is not the most recent, so we'll need to 574 // find it from the deep trenches of the "OLD" directory 575 // structure. 576 // 577 realVersion = version; 578 File dir = findOldPageDir( page ); 579 580 if( !dir.exists() || !dir.isDirectory() ) 581 { 582 return null; 583 } 584 585 File file = new File( dir, version+FILE_EXT ); 586 587 if( file.exists() ) 588 { 589 p = new WikiPage( m_engine, page ); 590 591 p.setLastModified( new Date(file.lastModified()) ); 592 p.setVersion( version ); 593 } 594 } 595 596 // 597 // Get author and other metadata information 598 // (Modification date has already been set.) 599 // 600 if( p != null ) 601 { 602 try 603 { 604 Properties props = getPageProperties( page ); 605 String author = props.getProperty( realVersion+".author" ); 606 if ( author == null ) 607 { 608 // we might not have a versioned author because the 609 // old page was last maintained by FileSystemProvider 610 Properties props2 = getHeritagePageProperties( page ); 611 author = props2.getProperty( WikiPage.AUTHOR ); 612 } 613 if ( author != null ) 614 { 615 p.setAuthor( author ); 616 } 617 618 String changenote = props.getProperty( realVersion+".changenote" ); 619 if( changenote != null ) p.setAttribute( WikiPage.CHANGENOTE, changenote ); 620 621 // Set the props values to the page attributes 622 setCustomProperties(p, props); 623 } 624 catch( IOException e ) 625 { 626 log.error( "Cannot get author for page"+page+": ", e ); 627 } 628 } 629 630 return p; 631 } 632 633 /** 634 * {@inheritDoc} 635 */ 636 @Override 637 public boolean pageExists( String pageName, int version ) 638 { 639 if (version == WikiPageProvider.LATEST_VERSION || version == findLatestVersion( pageName ) ) { 640 return pageExists(pageName); 641 } 642 643 File dir = findOldPageDir( pageName ); 644 645 if( !dir.exists() || !dir.isDirectory() ) 646 { 647 return false; 648 } 649 650 File file = new File( dir, version+FILE_EXT ); 651 652 return file.exists(); 653 654 } 655 656 /** 657 * {@inheritDoc} 658 */ 659 // FIXME: Does not get user information. 660 @Override 661 public List< WikiPage > getVersionHistory( String page ) throws ProviderException { 662 ArrayList<WikiPage> list = new ArrayList<>(); 663 int latest = findLatestVersion( page ); 664 665 // list.add( getPageInfo(page,WikiPageProvider.LATEST_VERSION) ); 666 667 for( int i = latest; i > 0; i-- ) 668 { 669 WikiPage info = getPageInfo( page, i ); 670 671 if( info != null ) 672 { 673 list.add( info ); 674 } 675 } 676 677 return list; 678 } 679 680 /* 681 * Support for migration of simple properties created by the 682 * FileSystemProvider when coming under Versioning management. 683 * Simulate an initial version. 684 */ 685 private Properties getHeritagePageProperties( String page ) 686 throws IOException 687 { 688 File propertyFile = new File( getPageDirectory(), 689 mangleName(page) + FileSystemProvider.PROP_EXT ); 690 if ( propertyFile.exists() ) 691 { 692 long lastModified = propertyFile.lastModified(); 693 694 CachedProperties cp = m_cachedProperties; 695 if ( cp != null 696 && cp.m_page.equals(page) 697 && cp.m_lastModified == lastModified ) 698 { 699 return cp.m_props; 700 } 701 702 InputStream in = null; 703 try 704 { 705 in = new BufferedInputStream( 706 new FileInputStream( propertyFile )); 707 708 Properties props = new Properties(); 709 props.load(in); 710 711 String originalAuthor = props.getProperty(WikiPage.AUTHOR); 712 if ( originalAuthor.length() > 0 ) 713 { 714 // simulate original author as if already versioned 715 // but put non-versioned property in special cache too 716 props.setProperty( "1.author", originalAuthor ); 717 718 // The profiler showed the probability was very high 719 // that when calling for the history of a page the 720 // propertyfile would be read as much times as there were 721 // versions of that file. It is statistically likely the 722 // propertyfile will be examined many times before it is updated. 723 cp = new CachedProperties( page, props, propertyFile.lastModified() ); 724 m_cachedProperties = cp; // Atomic 725 } 726 727 return props; 728 } 729 finally 730 { 731 IOUtils.closeQuietly( in ); 732 } 733 } 734 735 return new Properties(); // Returns an empty object 736 } 737 738 /** 739 * Removes the relevant page directory under "OLD" -directory as well, 740 * but does not remove any extra subdirectories from it. It will only 741 * touch those files that it thinks to be WikiPages. 742 * 743 * @param page {@inheritDoc} 744 * @throws {@inheritDoc} 745 */ 746 // FIXME: Should log errors. 747 @Override 748 public void deletePage( String page ) 749 throws ProviderException 750 { 751 super.deletePage( page ); 752 753 File dir = findOldPageDir( page ); 754 755 if( dir.exists() && dir.isDirectory() ) 756 { 757 File[] files = dir.listFiles( new WikiFileFilter() ); 758 759 for( int i = 0; i < files.length; i++ ) 760 { 761 files[i].delete(); 762 } 763 764 File propfile = new File( dir, PROPERTYFILE ); 765 766 if( propfile.exists() ) 767 { 768 propfile.delete(); 769 } 770 771 dir.delete(); 772 } 773 } 774 775 /** 776 * {@inheritDoc} 777 * 778 * Deleting versions has never really worked, 779 * JSPWiki assumes that version histories are "not gappy". 780 * Using deleteVersion() is definitely not recommended. 781 * 782 */ 783 @Override 784 public void deleteVersion( String page, int version ) 785 throws ProviderException 786 { 787 File dir = findOldPageDir( page ); 788 789 int latest = findLatestVersion( page ); 790 791 if( version == WikiPageProvider.LATEST_VERSION || 792 version == latest || 793 (version == 1 && latest == -1) ) 794 { 795 // 796 // Delete the properties 797 // 798 try 799 { 800 Properties props = getPageProperties( page ); 801 props.remove( ((latest > 0) ? latest : 1)+".author" ); 802 putPageProperties( page, props ); 803 } 804 catch( IOException e ) 805 { 806 log.error("Unable to modify page properties",e); 807 throw new ProviderException("Could not modify page properties: " + e.getMessage()); 808 } 809 810 // We can let the FileSystemProvider take care 811 // of the actual deletion 812 super.deleteVersion( page, WikiPageProvider.LATEST_VERSION ); 813 814 // 815 // Copy the old file to the new location 816 // 817 latest = findLatestVersion( page ); 818 819 File pageDir = findOldPageDir( page ); 820 File previousFile = new File( pageDir, Integer.toString(latest)+FILE_EXT ); 821 822 InputStream in = null; 823 OutputStream out = null; 824 825 try 826 { 827 if( previousFile.exists() ) 828 { 829 in = new BufferedInputStream( new FileInputStream( previousFile ) ); 830 File pageFile = findPage(page); 831 out = new BufferedOutputStream( new FileOutputStream( pageFile ) ); 832 833 FileUtil.copyContents( in, out ); 834 835 // 836 // We need also to set the date, since we rely on this. 837 // 838 pageFile.setLastModified( previousFile.lastModified() ); 839 } 840 } 841 catch( IOException e ) 842 { 843 log.fatal("Something wrong with the page directory - you may have just lost data!",e); 844 } 845 finally 846 { 847 IOUtils.closeQuietly( in ); 848 IOUtils.closeQuietly( out ); 849 } 850 851 return; 852 } 853 854 File pageFile = new File( dir, ""+version+FILE_EXT ); 855 856 if( pageFile.exists() ) 857 { 858 if( !pageFile.delete() ) 859 { 860 log.error("Unable to delete page."); 861 } 862 } 863 else 864 { 865 throw new NoSuchVersionException("Page "+page+", version="+version); 866 } 867 } 868 869 /** 870 * {@inheritDoc} 871 */ 872 // FIXME: This is kinda slow, we should need to do this only once. 873 @Override 874 public Collection< WikiPage > getAllPages() throws ProviderException 875 { 876 Collection< WikiPage > pages = super.getAllPages(); 877 Collection< WikiPage > returnedPages = new ArrayList<>(); 878 879 for( Iterator< WikiPage > i = pages.iterator(); i.hasNext(); ) 880 { 881 WikiPage page = i.next(); 882 883 WikiPage info = getPageInfo( page.getName(), WikiProvider.LATEST_VERSION ); 884 885 returnedPages.add( info ); 886 } 887 888 return returnedPages; 889 } 890 891 /** 892 * {@inheritDoc} 893 */ 894 @Override 895 public String getProviderInfo() 896 { 897 return ""; 898 } 899 900 /** 901 * {@inheritDoc} 902 */ 903 @Override 904 public void movePage( String from, 905 String to ) 906 throws ProviderException 907 { 908 // Move the file itself 909 File fromFile = findPage( from ); 910 File toFile = findPage( to ); 911 912 fromFile.renameTo( toFile ); 913 914 // Move any old versions 915 File fromOldDir = findOldPageDir( from ); 916 File toOldDir = findOldPageDir( to ); 917 918 fromOldDir.renameTo( toOldDir ); 919 } 920 921 /* 922 * The profiler showed that when calling the history of a page, the 923 * propertyfile was read just as many times as there were versions 924 * of that file. The loading of a propertyfile is a cpu-intensive job. 925 * This Class holds onto the last propertyfile read, because the 926 * probability is high that the next call will with ask for the same 927 * propertyfile. The time it took to show a historypage with 267 928 * versions dropped by 300%. Although each propertyfile in a history 929 * could be cached, there is likely to be little performance gain over 930 * simply keeping the last one requested. 931 */ 932 private static class CachedProperties 933 { 934 String m_page; 935 Properties m_props; 936 long m_lastModified; 937 938 /* 939 * Because a Constructor is inherently synchronised, there is 940 * no need to synchronise the arguments. 941 * 942 * @param engine WikiEngine instance 943 * @param props Properties to use for initialization 944 */ 945 public CachedProperties(String pageName, Properties props, 946 long lastModified) { 947 if ( pageName == null ) 948 { 949 throw new NullPointerException ( "pageName must not be null!" ); 950 } 951 this.m_page = pageName; 952 if ( props == null ) 953 { 954 throw new NullPointerException ( "properties must not be null!" ); 955 } 956 m_props = props; 957 this.m_lastModified = lastModified; 958 } 959 } 960}