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