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