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 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 ( firstUpdate ) 495 { 496 // we might not yet have a versioned author because the 497 // old page was last maintained by FileSystemProvider 498 Properties props2 = getHeritagePageProperties( page.getName() ); 499 500 // remember the simulated original author (or something) 501 // in the new properties 502 authorFirst = props2.getProperty( "1.author", "unknown" ); 503 props.setProperty( "1.author", authorFirst ); 504 } 505 506 String newAuthor = page.getAuthor(); 507 if ( newAuthor == null ) 508 { 509 newAuthor = ( authorFirst != null ) ? authorFirst : "unknown"; 510 } 511 page.setAuthor(newAuthor); 512 props.setProperty( versionNumber + ".author", newAuthor ); 513 514 String changeNote = (String) page.getAttribute(WikiPage.CHANGENOTE); 515 if( changeNote != null ) 516 { 517 props.setProperty( versionNumber+".changenote", changeNote ); 518 } 519 520 // Get additional custom properties from page and add to props 521 getCustomProperties(page, props); 522 523 putPageProperties( page.getName(), props ); 524 } 525 catch( IOException e ) 526 { 527 log.error( "Saving failed", e ); 528 throw new ProviderException("Could not save page text: "+e.getMessage()); 529 } 530 } 531 532 /** 533 * {@inheritDoc} 534 */ 535 public WikiPage getPageInfo( String page, int version ) 536 throws ProviderException 537 { 538 int latest = findLatestVersion(page); 539 int realVersion; 540 541 WikiPage p = null; 542 543 if( version == WikiPageProvider.LATEST_VERSION || 544 version == latest || 545 (version == 1 && latest == -1) ) 546 { 547 // 548 // Yes, we need to talk to the top level directory 549 // to get this version. 550 // 551 // I am listening to Press Play On Tape's guitar version of 552 // the good old C64 "Wizardry" -tune at this moment. 553 // Oh, the memories... 554 // 555 realVersion = (latest >= 0) ? latest : 1; 556 557 p = super.getPageInfo( page, WikiPageProvider.LATEST_VERSION ); 558 559 if( p != null ) 560 { 561 p.setVersion( realVersion ); 562 } 563 } 564 else 565 { 566 // 567 // The file is not the most recent, so we'll need to 568 // find it from the deep trenches of the "OLD" directory 569 // structure. 570 // 571 realVersion = version; 572 File dir = findOldPageDir( page ); 573 574 if( !dir.exists() || !dir.isDirectory() ) 575 { 576 return null; 577 } 578 579 File file = new File( dir, version+FILE_EXT ); 580 581 if( file.exists() ) 582 { 583 p = new WikiPage( m_engine, page ); 584 585 p.setLastModified( new Date(file.lastModified()) ); 586 p.setVersion( version ); 587 } 588 } 589 590 // 591 // Get author and other metadata information 592 // (Modification date has already been set.) 593 // 594 if( p != null ) 595 { 596 try 597 { 598 Properties props = getPageProperties( page ); 599 String author = props.getProperty( realVersion+".author" ); 600 if ( author == null ) 601 { 602 // we might not have a versioned author because the 603 // old page was last maintained by FileSystemProvider 604 Properties props2 = getHeritagePageProperties( page ); 605 author = props2.getProperty( WikiPage.AUTHOR ); 606 } 607 if ( author != null ) 608 { 609 p.setAuthor( author ); 610 } 611 612 String changenote = props.getProperty( realVersion+".changenote" ); 613 if( changenote != null ) p.setAttribute( WikiPage.CHANGENOTE, changenote ); 614 615 // Set the props values to the page attributes 616 setCustomProperties(p, props); 617 } 618 catch( IOException e ) 619 { 620 log.error( "Cannot get author for page"+page+": ", e ); 621 } 622 } 623 624 return p; 625 } 626 627 /** 628 * {@inheritDoc} 629 */ 630 public boolean pageExists( String pageName, int version ) 631 { 632 if (version == WikiPageProvider.LATEST_VERSION || version == findLatestVersion( pageName ) ) { 633 return pageExists(pageName); 634 } 635 636 File dir = findOldPageDir( pageName ); 637 638 if( !dir.exists() || !dir.isDirectory() ) 639 { 640 return false; 641 } 642 643 File file = new File( dir, version+FILE_EXT ); 644 645 return file.exists(); 646 647 } 648 649 /** 650 * {@inheritDoc} 651 */ 652 // FIXME: Does not get user information. 653 public List getVersionHistory( String page ) 654 throws ProviderException 655 { 656 ArrayList<WikiPage> list = new ArrayList<WikiPage>(); 657 658 int latest = findLatestVersion( page ); 659 660 // list.add( getPageInfo(page,WikiPageProvider.LATEST_VERSION) ); 661 662 for( int i = latest; i > 0; i-- ) 663 { 664 WikiPage info = getPageInfo( page, i ); 665 666 if( info != null ) 667 { 668 list.add( info ); 669 } 670 } 671 672 return list; 673 } 674 675 /* 676 * Support for migration of simple properties created by the 677 * FileSystemProvider when coming under Versioning management. 678 * Simulate an initial version. 679 */ 680 private Properties getHeritagePageProperties( String page ) 681 throws IOException 682 { 683 File propertyFile = new File( getPageDirectory(), 684 mangleName(page) + FileSystemProvider.PROP_EXT ); 685 if ( propertyFile.exists() ) 686 { 687 long lastModified = propertyFile.lastModified(); 688 689 CachedProperties cp = m_cachedProperties; 690 if ( cp != null 691 && cp.m_page.equals(page) 692 && cp.m_lastModified == lastModified ) 693 { 694 return cp.m_props; 695 } 696 697 InputStream in = null; 698 try 699 { 700 in = new BufferedInputStream( 701 new FileInputStream( propertyFile )); 702 703 Properties props = new Properties(); 704 props.load(in); 705 706 String originalAuthor = props.getProperty(WikiPage.AUTHOR); 707 if ( originalAuthor.length() > 0 ) 708 { 709 // simulate original author as if already versioned 710 // but put non-versioned property in special cache too 711 props.setProperty( "1.author", originalAuthor ); 712 713 // The profiler showed the probability was very high 714 // that when calling for the history of a page the 715 // propertyfile would be read as much times as there were 716 // versions of that file. It is statistically likely the 717 // propertyfile will be examined many times before it is updated. 718 cp = new CachedProperties( page, props, propertyFile.lastModified() ); 719 m_cachedProperties = cp; // Atomic 720 } 721 722 return props; 723 } 724 finally 725 { 726 IOUtils.closeQuietly( in ); 727 } 728 } 729 730 return new Properties(); // Returns an empty object 731 } 732 733 /** 734 * Removes the relevant page directory under "OLD" -directory as well, 735 * but does not remove any extra subdirectories from it. It will only 736 * touch those files that it thinks to be WikiPages. 737 * 738 * @param page {@inheritDoc} 739 * @throws {@inheritDoc} 740 */ 741 // FIXME: Should log errors. 742 public void deletePage( String page ) 743 throws ProviderException 744 { 745 super.deletePage( page ); 746 747 File dir = findOldPageDir( page ); 748 749 if( dir.exists() && dir.isDirectory() ) 750 { 751 File[] files = dir.listFiles( new WikiFileFilter() ); 752 753 for( int i = 0; i < files.length; i++ ) 754 { 755 files[i].delete(); 756 } 757 758 File propfile = new File( dir, PROPERTYFILE ); 759 760 if( propfile.exists() ) 761 { 762 propfile.delete(); 763 } 764 765 dir.delete(); 766 } 767 } 768 769 /** 770 * {@inheritDoc} 771 * 772 * Deleting versions has never really worked, 773 * JSPWiki assumes that version histories are "not gappy". 774 * Using deleteVersion() is definitely not recommended. 775 * 776 */ 777 public void deleteVersion( String page, int version ) 778 throws ProviderException 779 { 780 File dir = findOldPageDir( page ); 781 782 int latest = findLatestVersion( page ); 783 784 if( version == WikiPageProvider.LATEST_VERSION || 785 version == latest || 786 (version == 1 && latest == -1) ) 787 { 788 // 789 // Delete the properties 790 // 791 try 792 { 793 Properties props = getPageProperties( page ); 794 props.remove( ((latest > 0) ? latest : 1)+".author" ); 795 putPageProperties( page, props ); 796 } 797 catch( IOException e ) 798 { 799 log.error("Unable to modify page properties",e); 800 throw new ProviderException("Could not modify page properties: " + e.getMessage()); 801 } 802 803 // We can let the FileSystemProvider take care 804 // of the actual deletion 805 super.deleteVersion( page, WikiPageProvider.LATEST_VERSION ); 806 807 // 808 // Copy the old file to the new location 809 // 810 latest = findLatestVersion( page ); 811 812 File pageDir = findOldPageDir( page ); 813 File previousFile = new File( pageDir, Integer.toString(latest)+FILE_EXT ); 814 815 InputStream in = null; 816 OutputStream out = null; 817 818 try 819 { 820 if( previousFile.exists() ) 821 { 822 in = new BufferedInputStream( new FileInputStream( previousFile ) ); 823 File pageFile = findPage(page); 824 out = new BufferedOutputStream( new FileOutputStream( pageFile ) ); 825 826 FileUtil.copyContents( in, out ); 827 828 // 829 // We need also to set the date, since we rely on this. 830 // 831 pageFile.setLastModified( previousFile.lastModified() ); 832 } 833 } 834 catch( IOException e ) 835 { 836 log.fatal("Something wrong with the page directory - you may have just lost data!",e); 837 } 838 finally 839 { 840 IOUtils.closeQuietly( in ); 841 IOUtils.closeQuietly( out ); 842 } 843 844 return; 845 } 846 847 File pageFile = new File( dir, ""+version+FILE_EXT ); 848 849 if( pageFile.exists() ) 850 { 851 if( !pageFile.delete() ) 852 { 853 log.error("Unable to delete page."); 854 } 855 } 856 else 857 { 858 throw new NoSuchVersionException("Page "+page+", version="+version); 859 } 860 } 861 862 /** 863 * {@inheritDoc} 864 */ 865 // FIXME: This is kinda slow, we should need to do this only once. 866 public Collection getAllPages() throws ProviderException 867 { 868 Collection pages = super.getAllPages(); 869 Collection<WikiPage> returnedPages = new ArrayList<WikiPage>(); 870 871 for( Iterator i = pages.iterator(); i.hasNext(); ) 872 { 873 WikiPage page = (WikiPage) i.next(); 874 875 WikiPage info = getPageInfo( page.getName(), WikiProvider.LATEST_VERSION ); 876 877 returnedPages.add( info ); 878 } 879 880 return returnedPages; 881 } 882 883 /** 884 * {@inheritDoc} 885 */ 886 public String getProviderInfo() 887 { 888 return ""; 889 } 890 891 /** 892 * {@inheritDoc} 893 */ 894 public void movePage( String from, 895 String to ) 896 throws ProviderException 897 { 898 // Move the file itself 899 File fromFile = findPage( from ); 900 File toFile = findPage( to ); 901 902 fromFile.renameTo( toFile ); 903 904 // Move any old versions 905 File fromOldDir = findOldPageDir( from ); 906 File toOldDir = findOldPageDir( to ); 907 908 fromOldDir.renameTo( toOldDir ); 909 } 910 911 /* 912 * The profiler showed that when calling the history of a page, the 913 * propertyfile was read just as many times as there were versions 914 * of that file. The loading of a propertyfile is a cpu-intensive job. 915 * This Class holds onto the last propertyfile read, because the 916 * probability is high that the next call will with ask for the same 917 * propertyfile. The time it took to show a historypage with 267 918 * versions dropped by 300%. Although each propertyfile in a history 919 * could be cached, there is likely to be little performance gain over 920 * simply keeping the last one requested. 921 */ 922 private static class CachedProperties 923 { 924 String m_page; 925 Properties m_props; 926 long m_lastModified; 927 928 /* 929 * Because a Constructor is inherently synchronised, there is 930 * no need to synchronise the arguments. 931 * 932 * @param engine WikiEngine instance 933 * @param props Properties to use for initialization 934 */ 935 public CachedProperties(String pageName, Properties props, 936 long lastModified) { 937 if ( pageName == null ) 938 { 939 throw new NullPointerException ( "pageName must not be null!" ); 940 } 941 this.m_page = pageName; 942 if ( props == null ) 943 { 944 throw new NullPointerException ( "properties must not be null!" ); 945 } 946 m_props = props; 947 this.m_lastModified = lastModified; 948 } 949 } 950}